summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/exec')
-rw-r--r--src/mongo/db/exec/SConscript4
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor.cpp2
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor.h8
-rw-r--r--src/mongo/db/exec/add_fields_projection_executor_test.cpp57
-rw-r--r--src/mongo/db/exec/batched_delete_stage.cpp10
-rw-r--r--src/mongo/db/exec/bucket_unpacker.cpp862
-rw-r--r--src/mongo/db/exec/bucket_unpacker.h122
-rw-r--r--src/mongo/db/exec/bucket_unpacker_test.cpp234
-rw-r--r--src/mongo/db/exec/collection_scan.cpp47
-rw-r--r--src/mongo/db/exec/collection_scan_common.h16
-rw-r--r--src/mongo/db/exec/delete_stage.cpp61
-rw-r--r--src/mongo/db/exec/document_value/document.cpp128
-rw-r--r--src/mongo/db/exec/document_value/document.h100
-rw-r--r--src/mongo/db/exec/document_value/document_internal.h62
-rw-r--r--src/mongo/db/exec/document_value/document_metadata_fields.cpp55
-rw-r--r--src/mongo/db/exec/document_value/document_metadata_fields.h152
-rw-r--r--src/mongo/db/exec/document_value/document_metadata_fields_test.cpp192
-rw-r--r--src/mongo/db/exec/document_value/document_value_test.cpp235
-rw-r--r--src/mongo/db/exec/document_value/document_value_test_util.h7
-rw-r--r--src/mongo/db/exec/document_value/value.cpp18
-rw-r--r--src/mongo/db/exec/document_value/value.h13
-rw-r--r--src/mongo/db/exec/document_value/value_comparator_test.cpp28
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.cpp7
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor.h26
-rw-r--r--src/mongo/db/exec/exclusion_projection_executor_test.cpp14
-rw-r--r--src/mongo/db/exec/fetch.cpp6
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor.cpp131
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor.h41
-rw-r--r--src/mongo/db/exec/inclusion_projection_executor_test.cpp70
-rw-r--r--src/mongo/db/exec/index_scan.cpp4
-rw-r--r--src/mongo/db/exec/multi_plan.cpp57
-rw-r--r--src/mongo/db/exec/multi_plan.h17
-rw-r--r--src/mongo/db/exec/or.cpp6
-rw-r--r--src/mongo/db/exec/projection.h1
-rw-r--r--src/mongo/db/exec/projection_executor.h15
-rw-r--r--src/mongo/db/exec/projection_executor_builder.cpp2
-rw-r--r--src/mongo/db/exec/projection_executor_builder.h1
-rw-r--r--src/mongo/db/exec/projection_executor_redaction_test.cpp208
-rw-r--r--src/mongo/db/exec/projection_executor_wildcard_access_test.cpp2
-rw-r--r--src/mongo/db/exec/projection_node.cpp42
-rw-r--r--src/mongo/db/exec/projection_node.h25
-rw-r--r--src/mongo/db/exec/sbe/SConscript9
-rw-r--r--src/mongo/db/exec/sbe/abt/abt_lower.cpp11
-rw-r--r--src/mongo/db/exec/sbe/expression_test_base.h18
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.cpp14
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.h46
-rw-r--r--src/mongo/db/exec/sbe/expressions/sbe_set_expressions_test.cpp30
-rw-r--r--src/mongo/db/exec/sbe/sbe_hash_agg_test.cpp203
-rw-r--r--src/mongo/db/exec/sbe/sbe_plan_size_test.cpp4
-rw-r--r--src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp7
-rw-r--r--src/mongo/db/exec/sbe/sbe_trial_run_tracker_test.cpp48
-rw-r--r--src/mongo/db/exec/sbe/size_estimator.h5
-rw-r--r--src/mongo/db/exec/sbe/stages/branch.cpp38
-rw-r--r--src/mongo/db/exec/sbe/stages/collection_helpers.h1
-rw-r--r--src/mongo/db/exec/sbe/stages/hash_agg.cpp595
-rw-r--r--src/mongo/db/exec/sbe/stages/hash_agg.h201
-rw-r--r--src/mongo/db/exec/sbe/stages/hash_lookup.cpp56
-rw-r--r--src/mongo/db/exec/sbe/stages/hash_lookup.h17
-rw-r--r--src/mongo/db/exec/sbe/stages/ix_scan.cpp39
-rw-r--r--src/mongo/db/exec/sbe/stages/ix_scan.h18
-rw-r--r--src/mongo/db/exec/sbe/stages/plan_stats.h7
-rw-r--r--src/mongo/db/exec/sbe/stages/scan.cpp20
-rw-r--r--src/mongo/db/exec/sbe/stages/scan.h16
-rw-r--r--src/mongo/db/exec/sbe/stages/sort.cpp4
-rw-r--r--src/mongo/db/exec/sbe/stages/union.cpp18
-rw-r--r--src/mongo/db/exec/sbe/util/spilling.cpp142
-rw-r--r--src/mongo/db/exec/sbe/util/spilling.h126
-rw-r--r--src/mongo/db/exec/sbe/values/slot.cpp110
-rw-r--r--src/mongo/db/exec/sbe/values/slot.h29
-rw-r--r--src/mongo/db/exec/sbe/values/value.cpp6
-rw-r--r--src/mongo/db/exec/sbe/values/value.h9
-rw-r--r--src/mongo/db/exec/sbe/values/value_builder.h34
-rw-r--r--src/mongo/db/exec/sbe/values/value_serialization_test.cpp38
-rw-r--r--src/mongo/db/exec/sbe/vm/arith.cpp114
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.cpp339
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h67
-rw-r--r--src/mongo/db/exec/scoped_timer.cpp23
-rw-r--r--src/mongo/db/exec/scoped_timer.h24
-rw-r--r--src/mongo/db/exec/skip.cpp8
-rw-r--r--src/mongo/db/exec/skip.h9
-rw-r--r--src/mongo/db/exec/sort_executor.h2
-rw-r--r--src/mongo/db/exec/stagedebug_cmd.cpp2
-rw-r--r--src/mongo/db/exec/text_or.cpp4
-rw-r--r--src/mongo/db/exec/update_stage.cpp68
-rw-r--r--src/mongo/db/exec/upsert_stage.cpp9
-rw-r--r--src/mongo/db/exec/working_set_common.cpp31
-rw-r--r--src/mongo/db/exec/write_stage_common.cpp10
-rw-r--r--src/mongo/db/exec/write_stage_common.h2
88 files changed, 1286 insertions, 4433 deletions
diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript
index fb803f2cb53..c2caf69f0f2 100644
--- a/src/mongo/db/exec/SConscript
+++ b/src/mongo/db/exec/SConscript
@@ -84,7 +84,6 @@ sortExecutorEnv.Library(
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/sorter/sorter_idl',
- '$BUILD_DIR/mongo/db/sorter/sorter_stats',
],
)
@@ -109,7 +108,7 @@ env.Library(
'stagedebug_cmd.cpp'
],
LIBDEPS=[
- "$BUILD_DIR/mongo/db/index/index_access_method",
+ "$BUILD_DIR/mongo/db/index/index_access_methods",
"$BUILD_DIR/mongo/db/query_exec",
],
LIBDEPS_PRIVATE=[
@@ -130,7 +129,6 @@ env.CppUnitTest(
"find_projection_executor_test.cpp",
"inclusion_projection_executor_test.cpp",
"projection_executor_builder_test.cpp",
- "projection_executor_redaction_test.cpp",
"projection_executor_test.cpp",
"projection_executor_utils_test.cpp",
"projection_executor_wildcard_access_test.cpp",
diff --git a/src/mongo/db/exec/add_fields_projection_executor.cpp b/src/mongo/db/exec/add_fields_projection_executor.cpp
index 067059167f5..a0fd7f08580 100644
--- a/src/mongo/db/exec/add_fields_projection_executor.cpp
+++ b/src/mongo/db/exec/add_fields_projection_executor.cpp
@@ -93,7 +93,7 @@ private:
const BSONObj& _rawObj;
// Tracks which paths we've seen to ensure no two paths conflict with each other.
- OrderedPathSet _seenPaths;
+ std::set<std::string, PathPrefixComparator> _seenPaths;
};
void ProjectionSpecValidator::uassertValid(const BSONObj& spec) {
diff --git a/src/mongo/db/exec/add_fields_projection_executor.h b/src/mongo/db/exec/add_fields_projection_executor.h
index b1349d715d6..c2c8e8481e3 100644
--- a/src/mongo/db/exec/add_fields_projection_executor.h
+++ b/src/mongo/db/exec/add_fields_projection_executor.h
@@ -94,9 +94,9 @@ public:
*/
void parse(const BSONObj& spec);
- Document serializeTransformation(boost::optional<ExplainOptions::Verbosity> explain,
- const SerializationOptions& options = {}) const final {
- return _root->serialize(explain, options);
+ Document serializeTransformation(
+ boost::optional<ExplainOptions::Verbosity> explain) const final {
+ return _root->serialize(explain);
}
/**
@@ -112,7 +112,7 @@ public:
}
DocumentSource::GetModPathsReturn getModifiedPaths() const final {
- OrderedPathSet computedPaths;
+ std::set<std::string> computedPaths;
StringMap<std::string> renamedPaths;
_root->reportComputedPaths(&computedPaths, &renamedPaths);
return {DocumentSource::GetModPathsReturn::Type::kFiniteSet,
diff --git a/src/mongo/db/exec/add_fields_projection_executor_test.cpp b/src/mongo/db/exec/add_fields_projection_executor_test.cpp
index 0870c53751a..007e5cc1ffb 100644
--- a/src/mongo/db/exec/add_fields_projection_executor_test.cpp
+++ b/src/mongo/db/exec/add_fields_projection_executor_test.cpp
@@ -685,62 +685,5 @@ TEST(AddFieldsProjectionExecutorExecutionTest, DoNotExtractComputedProjectionWit
addFields.serializeTransformation(boost::none));
}
-TEST(AddFieldsProjectionExecutorExecutionTest,
- ExtractComputedProjectionShouldNotHideDependentSubFields) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- AddFieldsProjectionExecutor addFields(expCtx);
- addFields.parse(BSON("obj"
- << "$myMeta"
- << "b" << BSON("$add" << BSON_ARRAY("$obj.a" << 1))));
-
- const std::set<StringData> reservedNames{};
- auto [extractedAddFields, deleteFlag] =
- addFields.extractComputedProjections("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(extractedAddFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection =
- Document(fromjson("{obj: '$myMeta', b: {$add: ['$obj.a', {$const: 1}]}}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, addFields.serializeTransformation(boost::none));
-}
-
-TEST(AddFieldsProjectionExecutorExecutionTest,
- ExtractComputedProjectionShouldNotHideDependentFieldsWithDottedSibling) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- AddFieldsProjectionExecutor addFields(expCtx);
- addFields.parse(BSON("a"
- << "$myMeta"
- << "c.b"
- << "$a.x"));
-
- const std::set<StringData> reservedNames{};
- auto [extractedAddFields, deleteFlag] =
- addFields.extractComputedProjections("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(extractedAddFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection = Document(fromjson("{a: '$myMeta', c: {b: '$a.x'}}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, addFields.serializeTransformation(boost::none));
-}
-
-TEST(AddFieldsProjectionExecutorExecutionTest, ExtractComputedProjectionShouldNotIncludeId) {
- boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
- AddFieldsProjectionExecutor addFields(expCtx);
- addFields.parse(BSON("a" << BSON("$sum" << BSON_ARRAY("$myMeta"
- << "$_id"))));
-
- const std::set<StringData> reservedNames{};
- auto [extractedAddFields, deleteFlag] =
- addFields.extractComputedProjections("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(extractedAddFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection = Document(fromjson("{a: {$sum: ['$myMeta', '$_id']}}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, addFields.serializeTransformation(boost::none));
-}
-
} // namespace
} // namespace mongo::projection_executor
diff --git a/src/mongo/db/exec/batched_delete_stage.cpp b/src/mongo/db/exec/batched_delete_stage.cpp
index fb8be63cd8a..78bfd05e352 100644
--- a/src/mongo/db/exec/batched_delete_stage.cpp
+++ b/src/mongo/db/exec/batched_delete_stage.cpp
@@ -242,16 +242,6 @@ PlanStage::StageState BatchedDeleteStage::_deleteBatch(WorkingSetID* out) {
wuow.commit();
} catch (const WriteConflictException&) {
return _prepareToRetryDrainAfterWCE(out, recordsThatNoLongerMatch);
- } catch (const ExceptionFor<ErrorCodes::StaleConfig>& ex) {
- if (ex->getVersionReceived() == ChunkVersion::IGNORED() && ex->getCriticalSectionSignal()) {
- // If ChunkVersion is IGNORED and we encountered a critical section, then yield, wait
- // for critical section to finish and then we'll resume the write from the point we had
- // left. We do this to prevent large multi-writes from repeatedly failing due to
- // StaleConfig and exhausting the mongos retry attempts.
- planExecutorShardingCriticalSectionFuture(opCtx()) = ex->getCriticalSectionSignal();
- return _prepareToRetryDrainAfterWCE(out, recordsThatNoLongerMatch);
- }
- throw;
}
incrementSSSMetricNoOverflow(batchedDeletesSSS.docs, docsDeleted);
diff --git a/src/mongo/db/exec/bucket_unpacker.cpp b/src/mongo/db/exec/bucket_unpacker.cpp
index 8bba2da9e4d..bf2f1444fea 100644
--- a/src/mongo/db/exec/bucket_unpacker.cpp
+++ b/src/mongo/db/exec/bucket_unpacker.cpp
@@ -34,7 +34,6 @@
#include "mongo/bson/util/bsoncolumn.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_algo.h"
-#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_expr.h"
#include "mongo/db/matcher/expression_geo.h"
#include "mongo/db/matcher/expression_internal_bucket_geo_within.h"
@@ -42,14 +41,9 @@
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
-#include "mongo/db/matcher/rewrite_expr.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/timeseries/timeseries_options.h"
-#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery
-
-#include "mongo/logv2/log.h"
-
namespace mongo {
using IneligiblePredicatePolicy = BucketSpec::IneligiblePredicatePolicy;
@@ -64,9 +58,6 @@ bool BucketSpec::fieldIsComputed(StringData field) const {
namespace {
-constexpr long long max32BitEpochMillis =
- static_cast<long long>(std::numeric_limits<uint32_t>::max()) * 1000;
-
/**
* Creates an ObjectId initialized with an appropriate timestamp corresponding to 'rhs' and
* returns it as a Value.
@@ -86,7 +77,7 @@ auto constructObjectIdValue(const BSONElement& rhs, int bucketMaxSpanSeconds) {
oid.init(date, maxOrMin == OIDInit::max);
return oid;
};
- // Make an ObjectId corresponding to a date value adjusted by the max bucket value for the
+ // Make an ObjectId cooresponding to a date value adjusted by the max bucket value for the
// time series view that this query operates on. This predicate can be used in a comparison
// to gauge a max value for a given bucket, rather than a min value.
auto makeMaxAdjustedDateOID = [&](auto&& date, auto&& maxOrMin) {
@@ -95,16 +86,9 @@ auto constructObjectIdValue(const BSONElement& rhs, int bucketMaxSpanSeconds) {
// Subtract max bucket range.
return makeDateOID(date - Seconds{bucketMaxSpanSeconds}, maxOrMin);
else
- // Since we're out of range, just make a predicate that is true for all dates.
- // We'll never use an OID for a date < 0 due to OID range limitations, so we set the
- // minimum date to 0.
- return makeDateOID(Date_t::fromMillisSinceEpoch(0LL), OIDInit::min);
+ // Since we're out of range, just make a predicate that is true for all date types.
+ return makeDateOID(Date_t::min(), OIDInit::min);
};
-
- // Because the OID timestamp is only 4 bytes, we can't convert larger dates
- invariant(rhs.date().toMillisSinceEpoch() >= 0LL);
- invariant(rhs.date().toMillisSinceEpoch() <= max32BitEpochMillis);
-
// An ObjectId consists of a 4-byte timestamp, as well as a unique value and a counter, thus
// two ObjectIds initialized with the same date will have different values. To ensure that we
// do not incorrectly include or exclude any buckets, depending on the operator we will
@@ -146,9 +130,9 @@ std::unique_ptr<MatchExpression> makeOr(std::vector<std::unique_ptr<MatchExpress
return std::make_unique<OrMatchExpression>(std::move(nontrivial));
}
-BucketSpec::BucketPredicate handleIneligible(IneligiblePredicatePolicy policy,
- const MatchExpression* matchExpr,
- StringData message) {
+std::unique_ptr<MatchExpression> handleIneligible(IneligiblePredicatePolicy policy,
+ const MatchExpression* matchExpr,
+ StringData message) {
switch (policy) {
case IneligiblePredicatePolicy::kError:
uasserted(
@@ -156,7 +140,7 @@ BucketSpec::BucketPredicate handleIneligible(IneligiblePredicatePolicy policy,
"Error translating non-metadata time-series predicate to operate on buckets: " +
message + ": " + matchExpr->serialize().toString());
case IneligiblePredicatePolicy::kIgnore:
- return {};
+ return nullptr;
}
MONGO_UNREACHABLE_TASSERT(5916307);
}
@@ -220,49 +204,40 @@ std::unique_ptr<MatchExpression> createTypeEqualityPredicate(
return makeOr(std::move(typeEqualityPredicates));
}
-// Checks for the situations when it's not possible to create a bucket-level predicate (against the
-// computed control values) for the given event-level predicate ('matchExpr').
-boost::optional<StringData> checkComparisonPredicateEligibility(
+std::unique_ptr<MatchExpression> createComparisonPredicate(
const ComparisonMatchExpressionBase* matchExpr,
- const StringData matchExprPath,
- const BSONElement& matchExprData,
const BucketSpec& bucketSpec,
- ExpressionContext::CollationMatchesDefault collationMatchesDefault) {
+ int bucketMaxSpanSeconds,
+ ExpressionContext::CollationMatchesDefault collationMatchesDefault,
+ boost::intrusive_ptr<ExpressionContext> pExpCtx,
+ bool haveComputedMetaField,
+ bool includeMetaField,
+ bool assumeNoMixedSchemaData,
+ IneligiblePredicatePolicy policy) {
using namespace timeseries;
+ const auto matchExprPath = matchExpr->path();
+ const auto matchExprData = matchExpr->getData();
+
// The control field's min and max are chosen using a field-order insensitive comparator, while
// MatchExpressions use a comparator that treats field-order as significant. Because of this we
// will not perform this optimization on queries with operands of compound types.
if (matchExprData.type() == BSONType::Object || matchExprData.type() == BSONType::Array)
- return "operand can't be an object or array"_sd;
+ return handleIneligible(policy, matchExpr, "operand can't be an object or array"_sd);
- const auto isTimeField = (matchExprPath == bucketSpec.timeField());
-
- // A bucket might contain events with the missing fields. These events aren't taken in account
- // when computing the control values for those fields. This design has two repercussions:
- // 1. MatchExpressions have special comparison semantics regarding null, in that {$eq: null}
- // will match all documents where the field is either null or missing. This semantics cannot
- // be represented in terms of comparisons against the min/max control values.
- // 2. Non-type-bracketing predicates, such as {$expr: {$lt(e): ['$x', 42]}} should evaluate to
- // "true" if "x" is missing, which also cannot be represented as a bucket-level predicate.
- // 1) time field cannot be empty.
- // 2) the only type less than null is MinKey, which is internal, so we don't need to guard
- // GT and GTE.
- // 3) for the buckets that might have mixed schema data, we'll compare the types of min and
- // max when _creating_ the bucket-level predicate (that check won't help with missing).
+ // MatchExpressions have special comparison semantics regarding null, in that {$eq: null} will
+ // match all documents where the field is either null or missing. Because this is different
+ // from both the comparison semantics that InternalExprComparison expressions and the control's
+ // min and max fields use, we will not perform this optimization on queries with null operands.
if (matchExprData.type() == BSONType::jstNULL)
- return "can't handle comparison to null"_sd;
- if (!isTimeField &&
- (matchExpr->matchType() == MatchExpression::INTERNAL_EXPR_LTE ||
- matchExpr->matchType() == MatchExpression::INTERNAL_EXPR_LT)) {
- return "can't handle a non-type-bracketing LT or LTE comparisons"_sd;
- }
+ return handleIneligible(policy, matchExpr, "can't handle {$eq: null}"_sd);
// The control field's min and max are chosen based on the collation of the collection. If the
// query's collation does not match the collection's collation and the query operand is a
// string or compound type (skipped above) we will not perform this optimization.
if (collationMatchesDefault == ExpressionContext::CollationMatchesDefault::kNo &&
matchExprData.type() == BSONType::String) {
- return "can't handle string comparison with a non-default collation"_sd;
+ return handleIneligible(
+ policy, matchExpr, "can't handle string comparison with a non-default collation"_sd);
}
// This function only handles time and measurement predicates--not metadata.
@@ -277,73 +252,38 @@ boost::optional<StringData> checkComparisonPredicateEligibility(
// We must avoid mapping predicates on fields computed via $addFields or a computed $project.
if (bucketSpec.fieldIsComputed(matchExprPath.toString())) {
- return "can't handle a computed field"_sd;
- }
-
- // We must avoid mapping predicates on fields removed by $project.
- if (!determineIncludeField(matchExprPath, bucketSpec.behavior(), bucketSpec.fieldSet())) {
- return "can't handle a field removed by projection"_sd;
+ return handleIneligible(policy, matchExpr, "can't handle a computed field");
}
+ const auto isTimeField = (matchExprPath == bucketSpec.timeField());
if (isTimeField && matchExprData.type() != BSONType::Date) {
- // TODO SERVER-84207: right now we will end up unpacking everything and applying the event
- // filter, which indeed would be either trivially true or trivially false but it won't be
- // optimized away.
- return "can't handle comparison of time field to a non-Date type"_sd;
+ // Users are not allowed to insert non-date measurements into time field. So this query
+ // would not match anything. We do not need to optimize for this case.
+ return handleIneligible(
+ policy,
+ matchExpr,
+ "This predicate will never be true, because the time field always contains a Date");
}
- return boost::none;
-}
-
-std::unique_ptr<MatchExpression> createComparisonPredicate(
- const ComparisonMatchExpressionBase* matchExpr,
- const BucketSpec& bucketSpec,
- int bucketMaxSpanSeconds,
- ExpressionContext::CollationMatchesDefault collationMatchesDefault,
- boost::intrusive_ptr<ExpressionContext> pExpCtx,
- bool haveComputedMetaField,
- bool includeMetaField,
- bool assumeNoMixedSchemaData,
- IneligiblePredicatePolicy policy) {
- using namespace timeseries;
- const auto matchExprPath = matchExpr->path();
- const auto matchExprData = matchExpr->getData();
-
- const auto error = checkComparisonPredicateEligibility(
- matchExpr, matchExprPath, matchExprData, bucketSpec, collationMatchesDefault);
- if (error) {
- return handleIneligible(policy, matchExpr, *error).loosePredicate;
- }
-
- const auto isTimeField = (matchExprPath == bucketSpec.timeField());
- auto minPath = std::string{kControlMinFieldNamePrefix} + matchExprPath;
- const StringData minPathStringData(minPath);
- auto maxPath = std::string{kControlMaxFieldNamePrefix} + matchExprPath;
- const StringData maxPathStringData(maxPath);
-
BSONObj minTime;
BSONObj maxTime;
- bool dateIsExtended = false;
if (isTimeField) {
auto timeField = matchExprData.Date();
minTime = BSON("" << timeField - Seconds(bucketMaxSpanSeconds));
maxTime = BSON("" << timeField + Seconds(bucketMaxSpanSeconds));
-
- // The date is in the "extended" range if it doesn't fit into the bottom
- // 32 bits.
- long long timestamp = timeField.toMillisSinceEpoch();
- dateIsExtended = timestamp < 0LL || timestamp > max32BitEpochMillis;
}
+ auto minPath = std::string{kControlMinFieldNamePrefix} + matchExprPath;
+ auto maxPath = std::string{kControlMaxFieldNamePrefix} + matchExprPath;
+
switch (matchExpr->matchType()) {
case MatchExpression::EQ:
case MatchExpression::INTERNAL_EXPR_EQ:
// For $eq, make both a $lte against 'control.min' and a $gte predicate against
// 'control.max'.
//
- // If the comparison is against the 'time' field and we haven't stored a time outside of
- // the 32 bit range, include a predicate against the _id field which is converted to
- // the maximum for the corresponding range of ObjectIds and
+ // If the comparison is against the 'time' field, include a predicate against the _id
+ // field which is converted to the maximum for the corresponding range of ObjectIds and
// is adjusted by the max range for a bucket to approximate the max bucket value given
// the min. Also include a predicate against the _id field which is converted to the
// minimum for the range of ObjectIds corresponding to the given date. In
@@ -353,91 +293,60 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
//
// The same procedure applies to aggregation expressions of the form
// {$expr: {$eq: [...]}} that can be rewritten to use $_internalExprEq.
- if (!isTimeField) {
- return makeOr(makeVector<std::unique_ptr<MatchExpression>>(
- makePredicate(
- MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData)),
- createTypeEqualityPredicate(pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
- } else if (bucketSpec.usesExtendedRange()) {
- return makePredicate(
- MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
- minTime.firstElement()),
- MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
- maxTime.firstElement()));
- } else if (dateIsExtended) {
- // Since by this point we know that no time value has been inserted which is
- // outside the epoch range, we know that no document can meet this criteria
- return std::make_unique<AlwaysFalseMatchExpression>();
- } else {
- return makePredicate(
- MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
- minTime.firstElement()),
- MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
- maxTime.firstElement()),
- MatchExprPredicate<LTEMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<LTEMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)),
- MatchExprPredicate<GTEMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<GTEMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)));
- }
- MONGO_UNREACHABLE_TASSERT(6646903);
+ return isTimeField
+ ? makePredicate(
+ MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
+ MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
+ minTime.firstElement()),
+ MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
+ MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
+ maxTime.firstElement()),
+ MatchExprPredicate<LTEMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<LTEMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)),
+ MatchExprPredicate<GTEMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<GTEMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)))
+ : makeOr(makeVector<std::unique_ptr<MatchExpression>>(
+ makePredicate(MatchExprPredicate<InternalExprLTEMatchExpression>(
+ minPath, matchExprData),
+ MatchExprPredicate<InternalExprGTEMatchExpression>(
+ maxPath, matchExprData)),
+ createTypeEqualityPredicate(
+ pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
case MatchExpression::GT:
case MatchExpression::INTERNAL_EXPR_GT:
// For $gt, make a $gt predicate against 'control.max'. In addition, if the comparison
- // is against the 'time' field, and the collection doesn't contain times outside the
- // 32 bit range, include a predicate against the _id field which is converted to the
- // maximum for the corresponding range of ObjectIds and is adjusted by the max range
- // for a bucket to approximate the max bucket value given the min.
- //
- // In addition, we include a {'control.min' : {$gt: 'time - bucketMaxSpanSeconds'}}
+ // is against the 'time' field, include a predicate against the _id field which is
+ // converted to the maximum for the corresponding range of ObjectIds and is adjusted
+ // by the max range for a bucket to approximate the max bucket value given the min. In
+ // addition, we include a {'control.min' : {$gt: 'time - bucketMaxSpanSeconds'}}
// predicate which will be helpful in reducing bounds for index scans on 'time' field
// and routing on mongos.
//
// The same procedure applies to aggregation expressions of the form
// {$expr: {$gt: [...]}} that can be rewritten to use $_internalExprGt.
- if (!isTimeField) {
- return makeOr(makeVector<std::unique_ptr<MatchExpression>>(
- std::make_unique<InternalExprGTMatchExpression>(maxPath, matchExprData),
- createTypeEqualityPredicate(pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
- } else if (bucketSpec.usesExtendedRange()) {
- return makePredicate(
- MatchExprPredicate<InternalExprGTMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprGTMatchExpression>(minPath,
- minTime.firstElement()));
- } else if (matchExprData.Date().toMillisSinceEpoch() < 0LL) {
- // Since by this point we know that no time value has been inserted < 0,
- // every document must meet this criteria
- return std::make_unique<AlwaysTrueMatchExpression>();
- } else if (matchExprData.Date().toMillisSinceEpoch() > max32BitEpochMillis) {
- // Since by this point we know that no time value has been inserted >
- // max32BitEpochMillis, we know that no document can meet this criteria
- return std::make_unique<AlwaysFalseMatchExpression>();
- } else {
- return makePredicate(
- MatchExprPredicate<InternalExprGTMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprGTMatchExpression>(minPath,
- minTime.firstElement()),
- MatchExprPredicate<GTMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<GTMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)));
- }
- MONGO_UNREACHABLE_TASSERT(6646904);
+ return isTimeField
+ ? makePredicate(
+ MatchExprPredicate<InternalExprGTMatchExpression>(maxPath, matchExprData),
+ MatchExprPredicate<InternalExprGTMatchExpression>(minPath,
+ minTime.firstElement()),
+ MatchExprPredicate<GTMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<GTMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)))
+ : makeOr(makeVector<std::unique_ptr<MatchExpression>>(
+ std::make_unique<InternalExprGTMatchExpression>(maxPath, matchExprData),
+ createTypeEqualityPredicate(
+ pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
case MatchExpression::GTE:
case MatchExpression::INTERNAL_EXPR_GTE:
// For $gte, make a $gte predicate against 'control.max'. In addition, if the comparison
- // is against the 'time' field, and the collection doesn't contain times outside the
- // 32 bit range, include a predicate against the _id field which is
+ // is against the 'time' field, include a predicate against the _id field which is
// converted to the minimum for the corresponding range of ObjectIds and is adjusted
// by the max range for a bucket to approximate the max bucket value given the min. In
// addition, we include a {'control.min' : {$gte: 'time - bucketMaxSpanSeconds'}}
@@ -446,83 +355,49 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
//
// The same procedure applies to aggregation expressions of the form
// {$expr: {$gte: [...]}} that can be rewritten to use $_internalExprGte.
- if (!isTimeField) {
- return makeOr(makeVector<std::unique_ptr<MatchExpression>>(
- std::make_unique<InternalExprGTEMatchExpression>(maxPath, matchExprData),
- createTypeEqualityPredicate(pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
- } else if (bucketSpec.usesExtendedRange()) {
- return makePredicate(
- MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
- minTime.firstElement()));
- } else if (matchExprData.Date().toMillisSinceEpoch() < 0LL) {
- // Since by this point we know that no time value has been inserted < 0,
- // every document must meet this criteria
- return std::make_unique<AlwaysTrueMatchExpression>();
- } else if (matchExprData.Date().toMillisSinceEpoch() > max32BitEpochMillis) {
- // Since by this point we know that no time value has been inserted > 0xffffffff,
- // we know that no value can meet this criteria
- return std::make_unique<AlwaysFalseMatchExpression>();
- } else {
- return makePredicate(
- MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
- MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
- minTime.firstElement()),
- MatchExprPredicate<GTEMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<GTEMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)));
- }
- MONGO_UNREACHABLE_TASSERT(6646905);
+ return isTimeField
+ ? makePredicate(
+ MatchExprPredicate<InternalExprGTEMatchExpression>(maxPath, matchExprData),
+ MatchExprPredicate<InternalExprGTEMatchExpression>(minPath,
+ minTime.firstElement()),
+ MatchExprPredicate<GTEMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<GTEMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)))
+ : makeOr(makeVector<std::unique_ptr<MatchExpression>>(
+ std::make_unique<InternalExprGTEMatchExpression>(maxPath, matchExprData),
+ createTypeEqualityPredicate(
+ pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
case MatchExpression::LT:
case MatchExpression::INTERNAL_EXPR_LT:
// For $lt, make a $lt predicate against 'control.min'. In addition, if the comparison
// is against the 'time' field, include a predicate against the _id field which is
- // converted to the minimum for the corresponding range of ObjectIds, unless the
- // collection contain extended range dates which won't fit int the 32 bits allocated
- // for _id.
- //
- // In addition, we include a {'control.max' : {$lt: 'time + bucketMaxSpanSeconds'}}
+ // converted to the minimum for the corresponding range of ObjectIds. In
+ // addition, we include a {'control.max' : {$lt: 'time + bucketMaxSpanSeconds'}}
// predicate which will be helpful in reducing bounds for index scans on 'time' field
// and routing on mongos.
//
// The same procedure applies to aggregation expressions of the form
// {$expr: {$lt: [...]}} that can be rewritten to use $_internalExprLt.
- if (!isTimeField) {
- return makeOr(makeVector<std::unique_ptr<MatchExpression>>(
- std::make_unique<InternalExprLTMatchExpression>(minPath, matchExprData),
- createTypeEqualityPredicate(pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
- } else if (bucketSpec.usesExtendedRange()) {
- return makePredicate(
- MatchExprPredicate<InternalExprLTMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprLTMatchExpression>(maxPath,
- maxTime.firstElement()));
- } else if (matchExprData.Date().toMillisSinceEpoch() < 0LL) {
- // Since by this point we know that no time value has been inserted < 0,
- // we know that no document can meet this criteria
- return std::make_unique<AlwaysFalseMatchExpression>();
- } else if (matchExprData.Date().toMillisSinceEpoch() > max32BitEpochMillis) {
- // Since by this point we know that no time value has been inserted > 0xffffffff
- // every time value must be less than this value
- return std::make_unique<AlwaysTrueMatchExpression>();
- } else {
- return makePredicate(
- MatchExprPredicate<InternalExprLTMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprLTMatchExpression>(maxPath,
- maxTime.firstElement()),
- MatchExprPredicate<LTMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<LTMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)));
- }
- MONGO_UNREACHABLE_TASSERT(6646906);
+ return isTimeField
+ ? makePredicate(
+ MatchExprPredicate<InternalExprLTMatchExpression>(minPath, matchExprData),
+ MatchExprPredicate<InternalExprLTMatchExpression>(maxPath,
+ maxTime.firstElement()),
+ MatchExprPredicate<LTMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<LTMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)))
+ : makeOr(makeVector<std::unique_ptr<MatchExpression>>(
+ std::make_unique<InternalExprLTMatchExpression>(minPath, matchExprData),
+ createTypeEqualityPredicate(
+ pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
case MatchExpression::LTE:
case MatchExpression::INTERNAL_EXPR_LTE:
// For $lte, make a $lte predicate against 'control.min'. In addition, if the comparison
- // is against the 'time' field, and the collection doesn't contain times outside the
- // 32 bit range, include a predicate against the _id field which is
+ // is against the 'time' field, include a predicate against the _id field which is
// converted to the maximum for the corresponding range of ObjectIds. In
// addition, we include a {'control.max' : {$lte: 'time + bucketMaxSpanSeconds'}}
// predicate which will be helpful in reducing bounds for index scans on 'time' field
@@ -530,34 +405,19 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
//
// The same procedure applies to aggregation expressions of the form
// {$expr: {$lte: [...]}} that can be rewritten to use $_internalExprLte.
- if (!isTimeField) {
- return makeOr(makeVector<std::unique_ptr<MatchExpression>>(
- std::make_unique<InternalExprLTEMatchExpression>(minPath, matchExprData),
- createTypeEqualityPredicate(pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
- } else if (bucketSpec.usesExtendedRange()) {
- return makePredicate(
- MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
- maxTime.firstElement()));
- } else if (matchExprData.Date().toMillisSinceEpoch() < 0LL) {
- // Since by this point we know that no time value has been inserted < 0,
- // we know that no document can meet this criteria
- return std::make_unique<AlwaysFalseMatchExpression>();
- } else if (matchExprData.Date().toMillisSinceEpoch() > max32BitEpochMillis) {
- // Since by this point we know that no time value has been inserted > 0xffffffff
- // every document must be less than this value
- return std::make_unique<AlwaysTrueMatchExpression>();
- } else {
- return makePredicate(
- MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
- MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
- maxTime.firstElement()),
- MatchExprPredicate<LTEMatchExpression, Value>(
- kBucketIdFieldName,
- constructObjectIdValue<LTEMatchExpression>(matchExprData,
- bucketMaxSpanSeconds)));
- }
- MONGO_UNREACHABLE_TASSERT(6646907);
+ return isTimeField
+ ? makePredicate(
+ MatchExprPredicate<InternalExprLTEMatchExpression>(minPath, matchExprData),
+ MatchExprPredicate<InternalExprLTEMatchExpression>(maxPath,
+ maxTime.firstElement()),
+ MatchExprPredicate<LTEMatchExpression, Value>(
+ kBucketIdFieldName,
+ constructObjectIdValue<LTEMatchExpression>(matchExprData,
+ bucketMaxSpanSeconds)))
+ : makeOr(makeVector<std::unique_ptr<MatchExpression>>(
+ std::make_unique<InternalExprLTEMatchExpression>(minPath, matchExprData),
+ createTypeEqualityPredicate(
+ pExpCtx, matchExprPath, assumeNoMixedSchemaData)));
default:
MONGO_UNREACHABLE_TASSERT(5348302);
@@ -566,119 +426,9 @@ std::unique_ptr<MatchExpression> createComparisonPredicate(
MONGO_UNREACHABLE_TASSERT(5348303);
}
-std::unique_ptr<MatchExpression> createTightComparisonPredicate(
- const ComparisonMatchExpressionBase* matchExpr,
- const BucketSpec& bucketSpec,
- ExpressionContext::CollationMatchesDefault collationMatchesDefault) {
- using namespace timeseries;
- const auto matchExprPath = matchExpr->path();
- const auto matchExprData = matchExpr->getData();
-
- const auto error = checkComparisonPredicateEligibility(
- matchExpr, matchExprPath, matchExprData, bucketSpec, collationMatchesDefault);
- if (error) {
- return handleIneligible(BucketSpec::IneligiblePredicatePolicy::kIgnore, matchExpr, *error)
- .loosePredicate;
- }
-
- // We have to disable the tight predicate for the measurement field. There might be missing
- // values in the measurements and the control fields ignore them on insertion. So we cannot use
- // bucket min and max to determine the property of all events in the bucket. For measurement
- // fields, there's a further problem that if the control field is an array, we cannot generate
- // the tight predicate because the predicate will be implicitly mapped over the array elements.
- if (matchExprPath != bucketSpec.timeField()) {
- return handleIneligible(BucketSpec::IneligiblePredicatePolicy::kIgnore,
- matchExpr,
- "can't create tight predicate on non-time field")
- .tightPredicate;
- }
-
- auto minPath = std::string{kControlMinFieldNamePrefix} + matchExprPath;
- const StringData minPathStringData(minPath);
- auto maxPath = std::string{kControlMaxFieldNamePrefix} + matchExprPath;
- const StringData maxPathStringData(maxPath);
-
- switch (matchExpr->matchType()) {
- // All events satisfy $eq if bucket min and max both satisfy $eq.
- case MatchExpression::EQ:
- return makePredicate(
- MatchExprPredicate<EqualityMatchExpression>(minPathStringData, matchExprData),
- MatchExprPredicate<EqualityMatchExpression>(maxPathStringData, matchExprData));
- case MatchExpression::INTERNAL_EXPR_EQ:
- return makePredicate(
- MatchExprPredicate<InternalExprEqMatchExpression>(minPathStringData, matchExprData),
- MatchExprPredicate<InternalExprEqMatchExpression>(maxPathStringData,
- matchExprData));
-
- // All events satisfy $gt if bucket min satisfy $gt.
- case MatchExpression::GT:
- return std::make_unique<GTMatchExpression>(minPathStringData, matchExprData);
- case MatchExpression::INTERNAL_EXPR_GT:
- return std::make_unique<InternalExprGTMatchExpression>(minPathStringData,
- matchExprData);
-
- // All events satisfy $gte if bucket min satisfy $gte.
- case MatchExpression::GTE:
- return std::make_unique<GTEMatchExpression>(minPathStringData, matchExprData);
- case MatchExpression::INTERNAL_EXPR_GTE:
- return std::make_unique<InternalExprGTEMatchExpression>(minPathStringData,
- matchExprData);
-
- // All events satisfy $lt if bucket max satisfy $lt.
- case MatchExpression::LT:
- return std::make_unique<LTMatchExpression>(maxPathStringData, matchExprData);
- case MatchExpression::INTERNAL_EXPR_LT:
- return std::make_unique<InternalExprLTMatchExpression>(maxPathStringData,
- matchExprData);
-
- // All events satisfy $lte if bucket max satisfy $lte.
- case MatchExpression::LTE:
- return std::make_unique<LTEMatchExpression>(maxPathStringData, matchExprData);
- case MatchExpression::INTERNAL_EXPR_LTE:
- return std::make_unique<InternalExprLTEMatchExpression>(maxPathStringData,
- matchExprData);
-
- default:
- MONGO_UNREACHABLE_TASSERT(7026901);
- }
-}
-
-std::unique_ptr<MatchExpression> createTightExprTimeFieldPredicate(
- const ExprMatchExpression* matchExpr,
- const BucketSpec& bucketSpec,
- ExpressionContext::CollationMatchesDefault collationMatchesDefault,
- boost::intrusive_ptr<ExpressionContext> pExpCtx) {
- using namespace timeseries;
- RewriteExpr::RewriteResult rewriteRes =
- RewriteExpr::rewrite(matchExpr->getExpression(), pExpCtx->getCollator());
- auto unownedExpr = rewriteRes.matchExpression();
-
- // There might be children in the $and expression that cannot be rewritten to a match
- // expression. If this is the case we cannot assume that the tight predicate or
- // wholeBucketFilter produced by the rewritten $and expression is correct. Measurements in the
- // bucket might fit the rewritten $and expression, but fail to fit the other children of the
- // $and expression and will be returned incorrectly.
-
- // It is an error to call 'createPredicate' on predicates on the meta field, and it only
- // returns a value for predicates on the 'timeField'.
- if (unownedExpr && rewriteRes.allSubExpressionsRewritten() &&
- unownedExpr->path() == bucketSpec.timeField() &&
- ComparisonMatchExpressionBase::isInternalExprComparison(unownedExpr->matchType())) {
- const auto compareMatchExpr =
- checked_cast<const ComparisonMatchExpressionBase*>(unownedExpr);
- return createTightComparisonPredicate(
- compareMatchExpr, bucketSpec, collationMatchesDefault);
- }
-
- return handleIneligible(BucketSpec::IneligiblePredicatePolicy::kIgnore,
- matchExpr,
- "can only handle comparison $expr match expressions on the timeField")
- .tightPredicate;
-}
-
} // namespace
-BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
+std::unique_ptr<MatchExpression> BucketSpec::createPredicatesOnBucketLevelField(
const MatchExpression* matchExpr,
const BucketSpec& bucketSpec,
int bucketMaxSpanSeconds,
@@ -693,8 +443,7 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
// If we have a leaf predicate on a meta field, we can map it to the bucket's meta field.
// This includes comparisons such as $eq and $lte, as well as other non-comparison predicates
- // such as $exists, or $mod. Unrenamable expressions can't be split into a whole bucket level
- // filter, when we should return nullptr.
+ // such as $exists, $mod, or $elemMatch.
//
// Metadata predicates are partially handled earlier, by splitting the match expression into a
// metadata-only part, and measurement/time-only part. However, splitting a $match into two
@@ -712,65 +461,39 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
if (!includeMetaField)
return handleIneligible(policy, matchExpr, "cannot handle an excluded meta field");
- if (expression::hasOnlyRenameableMatchExpressionChildren(*matchExpr)) {
- auto looseResult = matchExpr->shallowClone();
- expression::applyRenamesToExpression(
- looseResult.get(),
- {{bucketSpec.metaField().value(), timeseries::kBucketMetaFieldName.toString()}});
- auto tightResult = looseResult->shallowClone();
- return {std::move(looseResult), std::move(tightResult)};
- } else {
- return {nullptr, nullptr};
- }
+ auto result = matchExpr->shallowClone();
+ expression::applyRenamesToExpression(
+ result.get(),
+ {{bucketSpec.metaField().get(), timeseries::kBucketMetaFieldName.toString()}});
+ return result;
}
if (matchExpr->matchType() == MatchExpression::AND) {
auto nextAnd = static_cast<const AndMatchExpression*>(matchExpr);
- auto looseAndExpression = std::make_unique<AndMatchExpression>();
- auto tightAndExpression = std::make_unique<AndMatchExpression>();
- for (size_t i = 0; i < nextAnd->numChildren(); i++) {
- auto child = createPredicatesOnBucketLevelField(nextAnd->getChild(i),
- bucketSpec,
- bucketMaxSpanSeconds,
- collationMatchesDefault,
- pExpCtx,
- haveComputedMetaField,
- includeMetaField,
- assumeNoMixedSchemaData,
- policy);
- if (child.loosePredicate) {
- looseAndExpression->add(std::move(child.loosePredicate));
- }
+ auto andMatchExpr = std::make_unique<AndMatchExpression>();
- if (tightAndExpression && child.tightPredicate) {
- tightAndExpression->add(std::move(child.tightPredicate));
- } else {
- // For tight expression, null means always false, we can short circuit here.
- tightAndExpression = nullptr;
+ for (size_t i = 0; i < nextAnd->numChildren(); i++) {
+ if (auto child = createPredicatesOnBucketLevelField(nextAnd->getChild(i),
+ bucketSpec,
+ bucketMaxSpanSeconds,
+ collationMatchesDefault,
+ pExpCtx,
+ haveComputedMetaField,
+ includeMetaField,
+ assumeNoMixedSchemaData,
+ policy)) {
+ andMatchExpr->add(std::move(child));
}
}
-
- // For a loose predicate, if we are unable to generate an expression we can just treat it as
- // always true or an empty AND. This is because we are trying to generate a predicate that
- // will match the superset of our actual results.
- std::unique_ptr<MatchExpression> looseExpression = nullptr;
- if (looseAndExpression->numChildren() == 1) {
- looseExpression = looseAndExpression->releaseChild(0);
- } else if (looseAndExpression->numChildren() > 1) {
- looseExpression = std::move(looseAndExpression);
+ if (andMatchExpr->numChildren() == 1) {
+ return andMatchExpr->releaseChild(0);
}
-
- // For a tight predicate, if we are unable to generate an expression we can just treat it as
- // always false. This is because we are trying to generate a predicate that will match the
- // subset of our actual results.
- std::unique_ptr<MatchExpression> tightExpression = nullptr;
- if (tightAndExpression && tightAndExpression->numChildren() == 1) {
- tightExpression = tightAndExpression->releaseChild(0);
- } else {
- tightExpression = std::move(tightAndExpression);
+ if (andMatchExpr->numChildren() > 0) {
+ return andMatchExpr;
}
- return {std::move(looseExpression), std::move(tightExpression)};
+ // No error message here: an empty AND is valid.
+ return nullptr;
} else if (matchExpr->matchType() == MatchExpression::OR) {
// Given {$or: [A, B]}, suppose A, B can be pushed down as A', B'.
// If an event matches {$or: [A, B]} then either:
@@ -778,9 +501,9 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
// - it matches B, which means any bucket containing it matches B'
// So {$or: [A', B']} will capture all the buckets we need to satisfy {$or: [A, B]}.
auto nextOr = static_cast<const OrMatchExpression*>(matchExpr);
- auto looseOrExpression = std::make_unique<OrMatchExpression>();
- auto tightOrExpression = std::make_unique<OrMatchExpression>();
+ auto result = std::make_unique<OrMatchExpression>();
+ bool alwaysTrue = false;
for (size_t i = 0; i < nextOr->numChildren(); i++) {
auto child = createPredicatesOnBucketLevelField(nextOr->getChild(i),
bucketSpec,
@@ -791,86 +514,51 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
includeMetaField,
assumeNoMixedSchemaData,
policy);
- if (looseOrExpression && child.loosePredicate) {
- looseOrExpression->add(std::move(child.loosePredicate));
+ if (child) {
+ result->add(std::move(child));
} else {
- // For loose expression, null means always true, we can short circuit here.
- looseOrExpression = nullptr;
- }
+ // Since this argument is always-true, the entire OR is always-true.
+ alwaysTrue = true;
- // For tight predicate, we give a tighter bound so that all events in the bucket
- // either all matches A or all matches B.
- if (child.tightPredicate) {
- tightOrExpression->add(std::move(child.tightPredicate));
+ // Only short circuit if we're uninterested in reporting errors.
+ if (policy == IneligiblePredicatePolicy::kIgnore)
+ break;
}
}
+ if (alwaysTrue)
+ return nullptr;
- // For a loose predicate, if we are unable to generate an expression we can just treat it as
- // always true. This is because we are trying to generate a predicate that will match the
- // superset of our actual results.
- std::unique_ptr<MatchExpression> looseExpression = nullptr;
- if (looseOrExpression && looseOrExpression->numChildren() == 1) {
- looseExpression = looseOrExpression->releaseChild(0);
- } else {
- looseExpression = std::move(looseOrExpression);
- }
-
- // For a tight predicate, if we are unable to generate an expression we can just treat it as
- // always false or an empty OR. This is because we are trying to generate a predicate that
- // will match the subset of our actual results.
- std::unique_ptr<MatchExpression> tightExpression = nullptr;
- if (tightOrExpression->numChildren() == 1) {
- tightExpression = tightOrExpression->releaseChild(0);
- } else if (tightOrExpression->numChildren() > 1) {
- tightExpression = std::move(tightOrExpression);
- }
-
- return {std::move(looseExpression), std::move(tightExpression)};
+ // No special case for an empty OR: returning nullptr would be incorrect because it
+ // means 'always-true', here.
+ return result;
} else if (ComparisonMatchExpression::isComparisonMatchExpression(matchExpr) ||
ComparisonMatchExpressionBase::isInternalExprComparison(matchExpr->matchType())) {
- return {
- createComparisonPredicate(checked_cast<const ComparisonMatchExpressionBase*>(matchExpr),
- bucketSpec,
- bucketMaxSpanSeconds,
- collationMatchesDefault,
- pExpCtx,
- haveComputedMetaField,
- includeMetaField,
- assumeNoMixedSchemaData,
- policy),
- createTightComparisonPredicate(
- checked_cast<const ComparisonMatchExpressionBase*>(matchExpr),
- bucketSpec,
- collationMatchesDefault)};
- } else if (matchExpr->matchType() == MatchExpression::EXPRESSION) {
- return {
- // The loose predicate will be pushed before the unpacking which will be inspected by
- // the
- // query planner. Since the classic planner doesn't handle the $expr expression, we
- // don't
- // generate the loose predicate.
- nullptr,
- createTightExprTimeFieldPredicate(checked_cast<const ExprMatchExpression*>(matchExpr),
- bucketSpec,
- collationMatchesDefault,
- pExpCtx)};
+ return createComparisonPredicate(
+ checked_cast<const ComparisonMatchExpressionBase*>(matchExpr),
+ bucketSpec,
+ bucketMaxSpanSeconds,
+ collationMatchesDefault,
+ pExpCtx,
+ haveComputedMetaField,
+ includeMetaField,
+ assumeNoMixedSchemaData,
+ policy);
} else if (matchExpr->matchType() == MatchExpression::GEO) {
auto& geoExpr = static_cast<const GeoMatchExpression*>(matchExpr)->getGeoExpression();
if (geoExpr.getPred() == GeoExpression::WITHIN ||
geoExpr.getPred() == GeoExpression::INTERSECT) {
- return {std::make_unique<InternalBucketGeoWithinMatchExpression>(
- geoExpr.getGeometryPtr(), geoExpr.getField()),
- nullptr};
+ return std::make_unique<InternalBucketGeoWithinMatchExpression>(
+ geoExpr.getGeometryPtr(), geoExpr.getField());
}
} else if (matchExpr->matchType() == MatchExpression::EXISTS) {
if (assumeNoMixedSchemaData) {
// We know that every field that appears in an event will also appear in the min/max.
auto result = std::make_unique<AndMatchExpression>();
- result->add(std::make_unique<ExistsMatchExpression>(StringData(
- std::string{timeseries::kControlMinFieldNamePrefix} + matchExpr->path())));
- result->add(std::make_unique<ExistsMatchExpression>(StringData(
- std::string{timeseries::kControlMaxFieldNamePrefix} + matchExpr->path())));
- return {std::move(result), nullptr};
+ result->add(std::make_unique<ExistsMatchExpression>(
+ std::string{timeseries::kControlMinFieldNamePrefix} + matchExpr->path()));
+ result->add(std::make_unique<ExistsMatchExpression>(
+ std::string{timeseries::kControlMaxFieldNamePrefix} + matchExpr->path()));
+ return result;
} else {
// At time of writing, we only pass 'kError' when creating a partial index, and
// we know the collection will have no mixed-schema buckets by the time the index is
@@ -879,7 +567,7 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
"Can't push down {$exists: true} when the collection may have mixed-schema "
"buckets.",
policy != IneligiblePredicatePolicy::kError);
- return {};
+ return nullptr;
}
} else if (matchExpr->matchType() == MatchExpression::MATCH_IN) {
// {a: {$in: [X, Y]}} is equivalent to {$or: [ {a: X}, {a: Y} ]}.
@@ -921,11 +609,11 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField(
}
}
if (alwaysTrue)
- return {};
+ return nullptr;
// As above, no special case for an empty IN: returning nullptr would be incorrect because
// it means 'always-true', here.
- return {std::move(result), nullptr};
+ return result;
}
return handleIneligible(policy, matchExpr, "can't handle this predicate");
}
@@ -969,10 +657,12 @@ BSONObj BucketSpec::pushdownPredicate(
BucketSpec{
tsOptions.getTimeField().toString(),
metaField.map([](StringData s) { return s.toString(); }),
- // Since we are operating on a collection, not a query-result,
- // there are no inclusion/exclusion projections we need to apply
- // to the buckets before unpacking. So we can use default values for the rest of
- // the arguments.
+ // Since we are operating on a collection, not a query-result, there are no
+ // inclusion/exclusion projections we need to apply to the buckets before
+ // unpacking.
+ {},
+ // And there are no computed projections.
+ {},
},
maxSpanSeconds,
collationMatchesDefault,
@@ -981,14 +671,13 @@ BSONObj BucketSpec::pushdownPredicate(
includeMetaField,
assumeNoMixedSchemaData,
policy)
- .loosePredicate
: nullptr;
BSONObjBuilder result;
if (metaOnlyPredicate)
- metaOnlyPredicate->serialize(&result, {});
+ metaOnlyPredicate->serialize(&result);
if (bucketMetricPredicate)
- bucketMetricPredicate->serialize(&result, {});
+ bucketMetricPredicate->serialize(&result);
return result.obj();
}
@@ -1004,15 +693,11 @@ public:
const Value& metaValue,
bool includeTimeField,
bool includeMetaField) = 0;
- virtual bool getNext(BSONObjBuilder& builder,
- const BucketSpec& spec,
- const BSONElement& metaValue,
- bool includeTimeField,
- bool includeMetaField) = 0;
virtual void extractSingleMeasurement(MutableDocument& measurement,
int j,
const BucketSpec& spec,
const std::set<std::string>& unpackFieldsToIncludeExclude,
+ BucketUnpacker::Behavior behavior,
const BSONObj& bucket,
const Value& metaValue,
bool includeTimeField,
@@ -1060,15 +745,11 @@ public:
const Value& metaValue,
bool includeTimeField,
bool includeMetaField) override;
- bool getNext(BSONObjBuilder& builder,
- const BucketSpec& spec,
- const BSONElement& metaValue,
- bool includeTimeField,
- bool includeMetaField) override;
void extractSingleMeasurement(MutableDocument& measurement,
int j,
const BucketSpec& spec,
const std::set<std::string>& unpackFieldsToIncludeExclude,
+ BucketUnpacker::Behavior behavior,
const BSONObj& bucket,
const Value& metaValue,
bool includeTimeField,
@@ -1080,7 +761,7 @@ private:
BSONObjIterator _timeFieldIter;
// Iterators used to unpack the columns of the above bucket that are populated during the reset
- // phase according to the provided 'BucketSpec'.
+ // phase according to the provided 'Behavior' and 'BucketSpec'.
std::vector<std::pair<std::string, BSONObjIterator>> _fieldIters;
};
@@ -1150,37 +831,12 @@ bool BucketUnpackerV1::getNext(MutableDocument& measurement,
return _timeFieldIter.more();
}
-bool BucketUnpackerV1::getNext(BSONObjBuilder& builder,
- const BucketSpec& spec,
- const BSONElement& metaValue,
- bool includeTimeField,
- bool includeMetaField) {
- auto&& timeElem = _timeFieldIter.next();
- if (includeTimeField) {
- builder.appendAs(timeElem, spec.timeField());
- }
-
- // Includes metaField when we're instructed to do so and metaField value exists.
- if (includeMetaField && !metaValue.eoo()) {
- builder.appendAs(metaValue, *spec.metaField());
- }
-
- const auto& currentIdx = timeElem.fieldNameStringData();
- for (auto&& [colName, colIter] : _fieldIters) {
- if (auto&& elem = *colIter; colIter.more() && elem.fieldNameStringData() == currentIdx) {
- builder.appendAs(elem, colName);
- colIter.advance(elem);
- }
- }
-
- return _timeFieldIter.more();
-}
-
void BucketUnpackerV1::extractSingleMeasurement(
MutableDocument& measurement,
int j,
const BucketSpec& spec,
const std::set<std::string>& unpackFieldsToIncludeExclude,
+ BucketUnpacker::Behavior behavior,
const BSONObj& bucket,
const Value& metaValue,
bool includeTimeField,
@@ -1195,7 +851,7 @@ void BucketUnpackerV1::extractSingleMeasurement(
for (auto&& dataElem : dataRegion) {
auto colName = dataElem.fieldNameStringData();
- if (!determineIncludeField(colName, spec.behavior(), unpackFieldsToIncludeExclude)) {
+ if (!determineIncludeField(colName, behavior, unpackFieldsToIncludeExclude)) {
continue;
}
auto value = dataElem[targetIdx];
@@ -1223,15 +879,11 @@ public:
const Value& metaValue,
bool includeTimeField,
bool includeMetaField) override;
- bool getNext(BSONObjBuilder& builder,
- const BucketSpec& spec,
- const BSONElement& metaValue,
- bool includeTimeField,
- bool includeMetaField) override;
void extractSingleMeasurement(MutableDocument& measurement,
int j,
const BucketSpec& spec,
const std::set<std::string>& unpackFieldsToIncludeExclude,
+ BucketUnpacker::Behavior behavior,
const BSONObj& bucket,
const Value& metaValue,
bool includeTimeField,
@@ -1261,7 +913,7 @@ private:
ColumnStore _timeColumn;
// Iterators used to unpack the columns of the above bucket that are populated during the reset
- // phase according to the provided 'BucketSpec'.
+ // phase according to the provided 'Behavior' and 'BucketSpec'.
std::vector<ColumnStore> _fieldColumns;
// Element count
@@ -1316,43 +968,12 @@ bool BucketUnpackerV2::getNext(MutableDocument& measurement,
return _timeColumn.it != _timeColumn.end;
}
-bool BucketUnpackerV2::getNext(BSONObjBuilder& builder,
- const BucketSpec& spec,
- const BSONElement& metaValue,
- bool includeTimeField,
- bool includeMetaField) {
- // Get element and increment iterator
- const auto& timeElem = *_timeColumn.it;
- if (includeTimeField) {
- builder.appendAs(timeElem, spec.timeField());
- }
- ++_timeColumn.it;
-
- // Includes metaField when we're instructed to do so and metaField value exists.
- if (includeMetaField && !metaValue.eoo()) {
- builder.appendAs(metaValue, *spec.metaField());
- }
-
- for (auto& fieldColumn : _fieldColumns) {
- uassert(7026803,
- "Bucket unexpectedly contained fewer values than count",
- fieldColumn.it != fieldColumn.end);
- const BSONElement& elem = *fieldColumn.it;
- // EOO represents missing field
- if (!elem.eoo()) {
- builder.appendAs(elem, fieldColumn.column.name());
- }
- ++fieldColumn.it;
- }
-
- return _timeColumn.it != _timeColumn.end;
-}
-
void BucketUnpackerV2::extractSingleMeasurement(
MutableDocument& measurement,
int j,
const BucketSpec& spec,
const std::set<std::string>& unpackFieldsToIncludeExclude,
+ BucketUnpacker::Behavior behavior,
const BSONObj& bucket,
const Value& metaValue,
bool includeTimeField,
@@ -1388,16 +1009,12 @@ std::size_t BucketUnpackerV2::numberOfFields() {
BucketSpec::BucketSpec(const std::string& timeField,
const boost::optional<std::string>& metaField,
const std::set<std::string>& fields,
- Behavior behavior,
- const std::set<std::string>& computedProjections,
- bool usesExtendedRange)
+ const std::set<std::string>& computedProjections)
: _fieldSet(fields),
- _behavior(behavior),
_computedMetaProjFields(computedProjections),
_timeField(timeField),
_timeFieldHashed(FieldNameHasher().hashedFieldName(_timeField)),
- _metaField(metaField),
- _usesExtendedRange(usesExtendedRange) {
+ _metaField(metaField) {
if (_metaField) {
_metaFieldHashed = FieldNameHasher().hashedFieldName(*_metaField);
}
@@ -1405,12 +1022,10 @@ BucketSpec::BucketSpec(const std::string& timeField,
BucketSpec::BucketSpec(const BucketSpec& other)
: _fieldSet(other._fieldSet),
- _behavior(other._behavior),
_computedMetaProjFields(other._computedMetaProjFields),
_timeField(other._timeField),
_timeFieldHashed(HashedFieldName{_timeField, other._timeFieldHashed->hash()}),
- _metaField(other._metaField),
- _usesExtendedRange(other._usesExtendedRange) {
+ _metaField(other._metaField) {
if (_metaField) {
_metaFieldHashed = HashedFieldName{*_metaField, other._metaFieldHashed->hash()};
}
@@ -1418,12 +1033,10 @@ BucketSpec::BucketSpec(const BucketSpec& other)
BucketSpec::BucketSpec(BucketSpec&& other)
: _fieldSet(std::move(other._fieldSet)),
- _behavior(other._behavior),
_computedMetaProjFields(std::move(other._computedMetaProjFields)),
_timeField(std::move(other._timeField)),
_timeFieldHashed(HashedFieldName{_timeField, other._timeFieldHashed->hash()}),
- _metaField(std::move(other._metaField)),
- _usesExtendedRange(other._usesExtendedRange) {
+ _metaField(std::move(other._metaField)) {
if (_metaField) {
_metaFieldHashed = HashedFieldName{*_metaField, other._metaFieldHashed->hash()};
}
@@ -1432,7 +1045,6 @@ BucketSpec::BucketSpec(BucketSpec&& other)
BucketSpec& BucketSpec::operator=(const BucketSpec& other) {
if (&other != this) {
_fieldSet = other._fieldSet;
- _behavior = other._behavior;
_computedMetaProjFields = other._computedMetaProjFields;
_timeField = other._timeField;
_timeFieldHashed = HashedFieldName{_timeField, other._timeFieldHashed->hash()};
@@ -1440,7 +1052,6 @@ BucketSpec& BucketSpec::operator=(const BucketSpec& other) {
if (_metaField) {
_metaFieldHashed = HashedFieldName{*_metaField, other._metaFieldHashed->hash()};
}
- _usesExtendedRange = other._usesExtendedRange;
}
return *this;
}
@@ -1482,8 +1093,8 @@ BucketUnpacker::BucketUnpacker(BucketUnpacker&& other) = default;
BucketUnpacker::~BucketUnpacker() = default;
BucketUnpacker& BucketUnpacker::operator=(BucketUnpacker&& rhs) = default;
-BucketUnpacker::BucketUnpacker(BucketSpec spec) {
- setBucketSpec(std::move(spec));
+BucketUnpacker::BucketUnpacker(BucketSpec spec, Behavior unpackerBehavior) {
+ setBucketSpecAndBehavior(std::move(spec), unpackerBehavior);
}
void BucketUnpacker::addComputedMetaProjFields(const std::vector<StringData>& computedFieldNames) {
@@ -1492,7 +1103,7 @@ void BucketUnpacker::addComputedMetaProjFields(const std::vector<StringData>& co
// If we're already specifically including fields, we need to add the computed fields to
// the included field set to indicate they're in the output doc.
- if (_spec.behavior() == BucketSpec::Behavior::kInclude) {
+ if (_unpackerBehavior == BucketUnpacker::Behavior::kInclude) {
_spec.addIncludeExcludeField(field);
} else {
// Since exclude is applied after addComputedMetaProjFields, we must erase the new field
@@ -1533,27 +1144,6 @@ Document BucketUnpacker::getNext() {
return measurement.freeze();
}
-BSONObj BucketUnpacker::getNextBson() {
- tassert(7026800, "'getNextBson()' requires the bucket to be owned", _bucket.isOwned());
- tassert(7026801, "'getNextBson()' was called after the bucket has been exhausted", hasNext());
- tassert(7026802,
- "'getNextBson()' cannot return max and min time as metadata",
- !_includeMaxTimeAsMetadata && !_includeMinTimeAsMetadata);
-
- BSONObjBuilder builder;
- _hasNext = _unpackingImpl->getNext(
- builder, _spec, _metaBSONElem, _includeTimeField, _includeMetaField);
-
- // Add computed meta projections.
- for (auto&& name : _spec.computedMetaProjFields()) {
- if (_computedMetaProjections[name]) {
- builder.appendAs(_computedMetaProjections[name], name);
- }
- }
-
- return builder.obj();
-}
-
Document BucketUnpacker::extractSingleMeasurement(int j) {
tassert(5422101,
"'extractSingleMeasurment' expects j to be greater than or equal to zero and less than "
@@ -1565,6 +1155,7 @@ Document BucketUnpacker::extractSingleMeasurement(int j) {
j,
_spec,
fieldsToIncludeExcludeDuringUnpack(),
+ _unpackerBehavior,
_bucket,
_metaValue,
_includeTimeField,
@@ -1578,10 +1169,9 @@ Document BucketUnpacker::extractSingleMeasurement(int j) {
return measurement.freeze();
}
-void BucketUnpacker::reset(BSONObj&& bucket, bool bucketMatchedQuery) {
+void BucketUnpacker::reset(BSONObj&& bucket) {
_unpackingImpl.reset();
_bucket = std::move(bucket);
- _bucketMatchedQuery = bucketMatchedQuery;
uassert(5346510, "An empty bucket cannot be unpacked", !_bucket.isEmpty());
auto&& dataRegion = _bucket.getField(timeseries::kBucketDataFieldName).Obj();
@@ -1596,8 +1186,7 @@ void BucketUnpacker::reset(BSONObj&& bucket, bool bucketMatchedQuery) {
"The $_internalUnpackBucket stage requires the data region to have a timeField object",
timeFieldElem);
- _metaBSONElem = _bucket[timeseries::kBucketMetaFieldName];
- _metaValue = Value{_metaBSONElem};
+ _metaValue = Value{_bucket[timeseries::kBucketMetaFieldName]};
if (_spec.metaField()) {
// The spec indicates that there might be a metadata region. Missing metadata in
// measurements is expressed with missing metadata in a bucket. But we disallow undefined
@@ -1679,10 +1268,10 @@ void BucketUnpacker::reset(BSONObj&& bucket, bool bucketMatchedQuery) {
continue;
}
- // Includes a field when '_spec.behavior()' is 'kInclude' and it's found in 'fieldSet' or
- // _spec.behavior() is 'kExclude' and it's not found in 'fieldSet'.
+ // Includes a field when '_unpackerBehavior' is 'kInclude' and it's found in 'fieldSet' or
+ // _unpackerBehavior is 'kExclude' and it's not found in 'fieldSet'.
if (determineIncludeField(
- colName, _spec.behavior(), fieldsToIncludeExcludeDuringUnpack())) {
+ colName, _unpackerBehavior, fieldsToIncludeExcludeDuringUnpack())) {
_unpackingImpl->addField(elem);
}
}
@@ -1733,7 +1322,7 @@ int BucketUnpacker::computeMeasurementCount(const BSONObj& bucket, StringData ti
}
void BucketUnpacker::determineIncludeTimeField() {
- const bool isInclude = _spec.behavior() == BucketSpec::Behavior::kInclude;
+ const bool isInclude = _unpackerBehavior == BucketUnpacker::Behavior::kInclude;
const bool fieldSetContainsTime =
_spec.fieldSet().find(_spec.timeField()) != _spec.fieldSet().end();
@@ -1753,32 +1342,27 @@ void BucketUnpacker::eraseMetaFromFieldSetAndDetermineIncludeMeta() {
} else if (auto itr = _spec.fieldSet().find(*_spec.metaField());
itr != _spec.fieldSet().end()) {
_spec.removeIncludeExcludeField(*_spec.metaField());
- _includeMetaField = _spec.behavior() == BucketSpec::Behavior::kInclude;
+ _includeMetaField = _unpackerBehavior == BucketUnpacker::Behavior::kInclude;
} else {
- _includeMetaField = _spec.behavior() == BucketSpec::Behavior::kExclude;
+ _includeMetaField = _unpackerBehavior == BucketUnpacker::Behavior::kExclude;
}
}
-void BucketUnpacker::eraseUnneededComputedMetaProjFields() {
- // If this is an inclusion spec and the current computed field is not part of in the include
- // fields, it means the computed field should not be available after the current unpack stage.
- // Similarly, for exclusion spec, if the current computed field is part of the exclude fields,
- // the computed fields should not be available after the current unpack stage. This can happen
- // if there was a $project stage after a $addFields stage.
- bool removeIfInFieldSet = _spec.behavior() == BucketSpec::Behavior::kExclude;
- auto conditionToErase = [&](const std::string& computedField) {
- bool inFieldSet = _spec.fieldSet().find(computedField) != _spec.fieldSet().end();
- return inFieldSet == removeIfInFieldSet;
- };
- _spec.eraseIfPredTrueFromComputedMetaProjFields(conditionToErase);
+void BucketUnpacker::eraseExcludedComputedMetaProjFields() {
+ if (_unpackerBehavior == BucketUnpacker::Behavior::kExclude) {
+ for (const auto& field : _spec.fieldSet()) {
+ _spec.eraseFromComputedMetaProjFields(field);
+ }
+ }
}
-void BucketUnpacker::setBucketSpec(BucketSpec&& bucketSpec) {
+void BucketUnpacker::setBucketSpecAndBehavior(BucketSpec&& bucketSpec, Behavior behavior) {
+ _unpackerBehavior = behavior;
_spec = std::move(bucketSpec);
eraseMetaFromFieldSetAndDetermineIncludeMeta();
determineIncludeTimeField();
- eraseUnneededComputedMetaProjFields();
+ eraseExcludedComputedMetaProjFields();
_includeMinTimeAsMetadata = _spec.includeMinTimeAsMetadata;
_includeMaxTimeAsMetadata = _spec.includeMaxTimeAsMetadata;
@@ -1799,7 +1383,7 @@ const std::set<std::string>& BucketUnpacker::fieldsToIncludeExcludeDuringUnpack(
_unpackFieldsToIncludeExclude = std::set<std::string>();
const auto& metaProjFields = _spec.computedMetaProjFields();
- if (_spec.behavior() == BucketSpec::Behavior::kInclude) {
+ if (_unpackerBehavior == BucketUnpacker::Behavior::kInclude) {
// For include, we unpack fieldSet - metaProjFields.
for (auto&& field : _spec.fieldSet()) {
if (metaProjFields.find(field) == metaProjFields.cend()) {
diff --git a/src/mongo/db/exec/bucket_unpacker.h b/src/mongo/db/exec/bucket_unpacker.h
index 3c63d6abe49..287bd9f2540 100644
--- a/src/mongo/db/exec/bucket_unpacker.h
+++ b/src/mongo/db/exec/bucket_unpacker.h
@@ -54,18 +54,11 @@ namespace mongo {
*/
class BucketSpec {
public:
- // When unpackin buckets with kInclude we must produce measurements that contain the
- // set of fields. Otherwise, if the kExclude option is used, the measurements will include the
- // set difference between all fields in the bucket and the provided fields.
- enum class Behavior { kInclude, kExclude };
-
BucketSpec() = default;
BucketSpec(const std::string& timeField,
const boost::optional<std::string>& metaField,
const std::set<std::string>& fields = {},
- Behavior behavior = Behavior::kExclude,
- const std::set<std::string>& computedProjections = {},
- bool usesExtendedRange = false);
+ const std::set<std::string>& computedProjections = {});
BucketSpec(const BucketSpec&);
BucketSpec(BucketSpec&&);
@@ -99,14 +92,6 @@ public:
return _fieldSet;
}
- void setBehavior(Behavior behavior) {
- _behavior = behavior;
- }
-
- Behavior behavior() const {
- return _behavior;
- }
-
void addComputedMetaProjFields(const StringData& field) {
_computedMetaProjFields.emplace(field);
}
@@ -115,24 +100,8 @@ public:
return _computedMetaProjFields;
}
- // Remove fields that the predicate function evaluates to true for.
- void eraseIfPredTrueFromComputedMetaProjFields(const std::function<bool(std::string)> pred) {
- auto it = _computedMetaProjFields.begin();
- while (it != _computedMetaProjFields.end()) {
- if (pred(*it)) {
- it = _computedMetaProjFields.erase(it);
- } else {
- ++it;
- }
- }
- }
-
- void setUsesExtendedRange(bool usesExtendedRange) {
- _usesExtendedRange = usesExtendedRange;
- }
-
- bool usesExtendedRange() const {
- return _usesExtendedRange;
+ void eraseFromComputedMetaProjFields(const std::string& field) {
+ _computedMetaProjFields.erase(field);
}
// Returns whether 'field' depends on a pushed down $addFields or computed $project.
@@ -149,45 +118,27 @@ public:
kError,
};
- struct BucketPredicate {
- // A loose predicate is a predicate which returns true when any measures of a bucket
- // matches.
- std::unique_ptr<MatchExpression> loosePredicate;
-
- // A tight predicate is a predicate which returns true when all measures of a bucket
- // matches.
- std::unique_ptr<MatchExpression> tightPredicate;
- };
-
/**
- * Takes a predicate after $_internalUnpackBucket as an argument and attempts to rewrite it as
- * new predicates on the 'control' field. There will be a 'loose' predicate that will match if
- * some of the event field matches, also a 'tight' predicate that will match if all of the event
- * field matches.
+ * Takes a predicate after $_internalUnpackBucket on a bucketed field as an argument and
+ * attempts to map it to a new predicate on the 'control' field. For example, the predicate
+ * {a: {$gt: 5}} will generate the predicate {control.max.a: {$_internalExprGt: 5}}, which will
+ * be added before the $_internalUnpackBucket stage.
*
- * For example, the event level predicate {a: {$gt: 5}} will generate the loose predicate
- * {control.max.a: {$_internalExprGt: 5}}. The loose predicate will be added before the
- * $_internalUnpackBucket stage to filter out buckets with no match.
- *
- * Ideally, we'd like to add a tight predicate such as {control.min.a: {$_internalExprGt: 5}} to
- * evaluate the filter on bucket level to avoid unnecessary event level evaluation. However, a
- * bucket might contain events with missing fields that are skipped when computing the controls,
- * so in reality we only add a tight predicate on timeField which is required to exist.
- *
- * If the original predicate is on the bucket's timeField we may also create a new loose
- * predicate on the '_id' field (as it incorporates min time for the bucket) to assist in index
- * utilization. For example, the predicate {time: {$lt: new Date(...)}} will generate the
- * following predicate:
+ * If the original predicate is on the bucket's timeField we may also create a new predicate
+ * on the '_id' field to assist in index utilization. For example, the predicate
+ * {time: {$lt: new Date(...)}} will generate the following predicate:
* {$and: [
* {_id: {$lt: ObjectId(...)}},
* {control.min.time: {$_internalExprLt: new Date(...)}}
* ]}
*
- * If the provided predicate is ineligible for this mapping and using
- * IneligiblePredicatePolicy::kIgnore, both loose and tight predicates will be set to nullptr.
- * When using IneligiblePredicatePolicy::kError it raises a user error.
+ * If the provided predicate is ineligible for this mapping, the function will return a nullptr.
+ * This should be interpreted as an always-true predicate.
+ *
+ * When using IneligiblePredicatePolicy::kIgnore, if the predicate can't be pushed down, it
+ * returns null. When using IneligiblePredicatePolicy::kError it raises a user error.
*/
- static BucketPredicate createPredicatesOnBucketLevelField(
+ static std::unique_ptr<MatchExpression> createPredicatesOnBucketLevelField(
const MatchExpression* matchExpr,
const BucketSpec& bucketSpec,
int bucketMaxSpanSeconds,
@@ -233,7 +184,6 @@ public:
private:
// The set of field names in the data region that should be included or excluded.
std::set<std::string> _fieldSet;
- Behavior _behavior = Behavior::kExclude;
// Set of computed meta field projection names. Added at the end of materialized
// measurements.
@@ -244,7 +194,6 @@ private:
boost::optional<std::string> _metaField = boost::none;
boost::optional<HashedFieldName> _metaFieldHashed = boost::none;
- bool _usesExtendedRange = false;
};
/**
@@ -252,6 +201,10 @@ private:
*/
class BucketUnpacker {
public:
+ // When BucketUnpacker is created with kInclude it must produce measurements that contain the
+ // set of fields. Otherwise, if the kExclude option is used, the measurements will include the
+ // set difference between all fields in the bucket and the provided fields.
+ enum class Behavior { kInclude, kExclude };
/**
* Returns the number of measurements in the bucket in O(1) time.
*/
@@ -261,7 +214,7 @@ public:
static const std::set<StringData> reservedBucketFieldNames;
BucketUnpacker();
- BucketUnpacker(BucketSpec spec);
+ BucketUnpacker(BucketSpec spec, Behavior unpackerBehavior);
BucketUnpacker(const BucketUnpacker& other) = delete;
BucketUnpacker(BucketUnpacker&& other);
~BucketUnpacker();
@@ -275,11 +228,6 @@ public:
Document getNext();
/**
- * Similar to the previous method, but return a BSON object instead.
- */
- BSONObj getNextBson();
-
- /**
* This method will extract the j-th measurement from the bucket. A precondition of this method
* is that j >= 0 && j <= the number of measurements within the underlying bucket.
*/
@@ -298,6 +246,7 @@ public:
*/
BucketUnpacker copy() const {
BucketUnpacker unpackerCopy;
+ unpackerCopy._unpackerBehavior = _unpackerBehavior;
unpackerCopy._spec = _spec;
unpackerCopy._includeMetaField = _includeMetaField;
unpackerCopy._includeTimeField = _includeTimeField;
@@ -307,10 +256,10 @@ public:
/**
* This resets the unpacker to prepare to unpack a new bucket described by the given document.
*/
- void reset(BSONObj&& bucket, bool bucketMatchedQuery = false);
+ void reset(BSONObj&& bucket);
- BucketSpec::Behavior behavior() const {
- return _spec.behavior();
+ Behavior behavior() const {
+ return _unpackerBehavior;
}
const BucketSpec& bucketSpec() const {
@@ -321,10 +270,6 @@ public:
return _bucket;
}
- bool bucketMatchedQuery() const {
- return _bucketMatchedQuery;
- }
-
bool includeMetaField() const {
return _includeMetaField;
}
@@ -361,7 +306,7 @@ public:
return std::string{timeseries::kControlMaxFieldNamePrefix} + field;
}
- void setBucketSpec(BucketSpec&& bucketSpec);
+ void setBucketSpecAndBehavior(BucketSpec&& bucketSpec, Behavior behavior);
void setIncludeMinTimeAsMetadata();
void setIncludeMaxTimeAsMetadata();
@@ -382,19 +327,16 @@ private:
// included in the materialized measurements.
void eraseMetaFromFieldSetAndDetermineIncludeMeta();
- // Erase computed meta projection fields if they are present in the exclusion field set or if
- // they are not present in the inclusion set.
- void eraseUnneededComputedMetaProjFields();
+ // Erase computed meta projection fields if they are present in the exclusion field set.
+ void eraseExcludedComputedMetaProjFields();
BucketSpec _spec;
+ Behavior _unpackerBehavior;
std::unique_ptr<UnpackingImpl> _unpackingImpl;
bool _hasNext = false;
- // A flag used to mark that the entire bucket matches the following $match predicate.
- bool _bucketMatchedQuery = false;
-
// A flag used to mark that the timestamp value should be materialized in measurements.
bool _includeTimeField{false};
@@ -415,8 +357,6 @@ private:
// measurement.
Value _metaValue;
- BSONElement _metaBSONElem;
-
// Since the bucket min time is the same across all materialized measurements, we can cache the
// value in the reset phase and use it to materialize as a metadata field in each measurement
// if required by the pipeline.
@@ -443,9 +383,9 @@ private:
* Determines if an arbitrary field should be included in the materialized measurements.
*/
inline bool determineIncludeField(StringData fieldName,
- BucketSpec::Behavior unpackerBehavior,
+ BucketUnpacker::Behavior unpackerBehavior,
const std::set<std::string>& unpackFieldsToIncludeExclude) {
- const bool isInclude = unpackerBehavior == BucketSpec::Behavior::kInclude;
+ const bool isInclude = unpackerBehavior == BucketUnpacker::Behavior::kInclude;
const bool unpackFieldsContains = unpackFieldsToIncludeExclude.find(fieldName.toString()) !=
unpackFieldsToIncludeExclude.cend();
return isInclude == unpackFieldsContains;
diff --git a/src/mongo/db/exec/bucket_unpacker_test.cpp b/src/mongo/db/exec/bucket_unpacker_test.cpp
index 1d62aae3768..8ee0f4e05f5 100644
--- a/src/mongo/db/exec/bucket_unpacker_test.cpp
+++ b/src/mongo/db/exec/bucket_unpacker_test.cpp
@@ -57,12 +57,12 @@ public:
* before actually doing any unpacking.
*/
BucketUnpacker makeBucketUnpacker(std::set<std::string> fields,
- BucketSpec::Behavior behavior,
+ BucketUnpacker::Behavior behavior,
BSONObj bucket,
boost::optional<std::string> metaFieldName = boost::none) {
- auto spec =
- BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields), behavior};
- BucketUnpacker unpacker{std::move(spec)};
+ auto spec = BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields)};
+
+ BucketUnpacker unpacker{std::move(spec), behavior};
unpacker.reset(std::move(bucket));
return unpacker;
}
@@ -72,13 +72,12 @@ public:
* the given 'bucket'. Asserts that 'reset()' throws the given 'errorCode'.
*/
void assertUnpackerThrowsCode(std::set<std::string> fields,
- BucketSpec::Behavior behavior,
+ BucketUnpacker::Behavior behavior,
BSONObj bucket,
boost::optional<std::string> metaFieldName,
int errorCode) {
- auto spec =
- BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields), behavior};
- BucketUnpacker unpacker{std::move(spec)};
+ auto spec = BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields)};
+ BucketUnpacker unpacker{std::move(spec), behavior};
ASSERT_THROWS_CODE(unpacker.reset(std::move(bucket)), AssertionException, errorCode);
}
@@ -170,11 +169,6 @@ public:
}
return root.obj();
}
-
- bool computedMetaProjFieldsContainsField(std::set<std::string>& computedMetaProjFields,
- std::string field) {
- return computedMetaProjFields.find(field) != computedMetaProjFields.end();
- }
};
TEST_F(BucketUnpackerTest, UnpackBasicIncludeAllMeasurementFields) {
@@ -187,7 +181,7 @@ TEST_F(BucketUnpackerTest, UnpackBasicIncludeAllMeasurementFields) {
"a:{'0':1, '1':2}, b:{'1':1}}}");
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(bucket),
kUserDefinedMetaName.toString());
@@ -211,7 +205,7 @@ TEST_F(BucketUnpackerTest, ExcludeASingleField) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
@@ -239,7 +233,7 @@ TEST_F(BucketUnpackerTest, EmptyIncludeGetsEmptyMeasurements) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(bucket),
kUserDefinedMetaName.toString());
@@ -265,7 +259,7 @@ TEST_F(BucketUnpackerTest, EmptyExcludeMaterializesAllFields) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -293,7 +287,7 @@ TEST_F(BucketUnpackerTest, SparseColumnsWhereOneColumnIsExhaustedBeforeTheOther)
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -320,7 +314,7 @@ TEST_F(BucketUnpackerTest, UnpackBasicIncludeWithDollarPrefix) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -347,7 +341,7 @@ TEST_F(BucketUnpackerTest, BucketsWithMetadataOnly) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -373,7 +367,7 @@ TEST_F(BucketUnpackerTest, UnorderedRowKeysDoesntAffectMaterialization) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -403,7 +397,7 @@ TEST_F(BucketUnpackerTest, MissingMetaFieldDoesntMaterializeMetadata) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -430,7 +424,7 @@ TEST_F(BucketUnpackerTest, MissingMetaFieldDoesntMaterializeMetadataUnorderedKey
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -458,7 +452,7 @@ TEST_F(BucketUnpackerTest, ExcludedMetaFieldDoesntMaterializeMetadataWhenBucketH
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -485,7 +479,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnUndefinedMeta) {
auto test = [&](BSONObj bucket) {
assertUnpackerThrowsCode(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString(),
5369600);
@@ -505,7 +499,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnUnexpectedMeta) {
auto test = [&](BSONObj bucket) {
assertUnpackerThrowsCode(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
boost::none /* no metaField provided */,
5369601);
@@ -524,7 +518,7 @@ TEST_F(BucketUnpackerTest, NullMetaInBucketMaterializesAsNull) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -556,7 +550,7 @@ TEST_F(BucketUnpackerTest, GetNextHandlesMissingMetaInBucket) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(fields,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
std::move(bucket),
kUserDefinedMetaName.toString());
ASSERT_TRUE(unpacker.hasNext());
@@ -585,7 +579,7 @@ TEST_F(BucketUnpackerTest, EmptyDataRegionInBucketIsTolerated) {
auto test = [&](BSONObj bucket) {
auto unpacker = makeBucketUnpacker(
- fields, BucketSpec::Behavior::kExclude, bucket, kUserDefinedMetaName.toString());
+ fields, BucketUnpacker::Behavior::kExclude, bucket, kUserDefinedMetaName.toString());
ASSERT_FALSE(unpacker.hasNext());
};
@@ -597,7 +591,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnEmptyBucket) {
auto bucket = Document{};
assertUnpackerThrowsCode(std::move(fields),
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
bucket.toBson(),
kUserDefinedMetaName.toString(),
5346510);
@@ -615,125 +609,51 @@ TEST_F(BucketUnpackerTest, EraseMetaFromFieldSetAndDetermineIncludeMeta) {
}
})");
auto unpacker = makeBucketUnpacker(empFields,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(bucket),
kUserDefinedMetaName.toString());
// Tests a spec with 'metaField' in include list.
std::set<std::string> fields{kUserDefinedMetaName.toString()};
- auto specWithMetaInclude = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(fields),
- BucketSpec::Behavior::kInclude};
+ auto specWithMetaInclude = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
// This calls eraseMetaFromFieldSetAndDetermineIncludeMeta.
- unpacker.setBucketSpec(std::move(specWithMetaInclude));
+ unpacker.setBucketSpecAndBehavior(std::move(specWithMetaInclude),
+ BucketUnpacker::Behavior::kInclude);
ASSERT_TRUE(unpacker.includeMetaField());
ASSERT_EQ(unpacker.bucketSpec().fieldSet().count(kUserDefinedMetaName.toString()), 0);
std::set<std::string> fieldsNoMetaInclude{"foo"};
auto specWithFooInclude = BucketSpec{kUserDefinedTimeName.toString(),
kUserDefinedMetaName.toString(),
- std::move(fieldsNoMetaInclude),
- BucketSpec::Behavior::kInclude};
+ std::move(fieldsNoMetaInclude)};
std::set<std::string> fieldsNoMetaExclude{"foo"};
auto specWithFooExclude = BucketSpec{kUserDefinedTimeName.toString(),
kUserDefinedMetaName.toString(),
- std::move(fieldsNoMetaExclude),
- BucketSpec::Behavior::kExclude};
+ std::move(fieldsNoMetaExclude)};
- unpacker.setBucketSpec(std::move(specWithFooExclude));
+ unpacker.setBucketSpecAndBehavior(std::move(specWithFooExclude),
+ BucketUnpacker::Behavior::kExclude);
ASSERT_TRUE(unpacker.includeMetaField());
- unpacker.setBucketSpec(std::move(specWithFooInclude));
+ unpacker.setBucketSpecAndBehavior(std::move(specWithFooInclude),
+ BucketUnpacker::Behavior::kInclude);
ASSERT_FALSE(unpacker.includeMetaField());
// Tests a spec with 'metaField' not in exclude list.
std::set<std::string> excludeFields{};
- auto specMetaExclude = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(excludeFields),
- BucketSpec::Behavior::kExclude};
-
+ auto specMetaExclude = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(excludeFields)};
auto specMetaInclude = specMetaExclude;
- specMetaInclude.setBehavior(BucketSpec::Behavior::kInclude);
-
- unpacker.setBucketSpec(std::move(specMetaExclude));
+ unpacker.setBucketSpecAndBehavior(std::move(specMetaExclude),
+ BucketUnpacker::Behavior::kExclude);
ASSERT_TRUE(unpacker.includeMetaField());
- unpacker.setBucketSpec(std::move(specMetaInclude));
+ unpacker.setBucketSpecAndBehavior(std::move(specMetaInclude),
+ BucketUnpacker::Behavior::kInclude);
ASSERT_FALSE(unpacker.includeMetaField());
}
-TEST_F(BucketUnpackerTest, EraseUnneededComputedMetaProjFieldsWithInclusiveProject) {
- auto bucket = fromjson(R"(
-{
- control: {version: 1},
- data: {
- _id: {'0':4, '1':5, '2':6},
- time: {'0':4, '1': 5, '2': 6}
- }
-})");
- std::set<std::string> unpackerFields{kUserDefinedTimeName.toString()};
- auto unpacker = makeBucketUnpacker(unpackerFields,
- BucketSpec::Behavior::kInclude,
- std::move(bucket),
- kUserDefinedMetaName.toString());
-
- // Add fields to '_computedMetaProjFields'.
- unpacker.addComputedMetaProjFields({"hello"_sd, "bye"_sd});
- auto computedMetaProjFields = unpacker.bucketSpec().computedMetaProjFields();
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "hello"));
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "bye"));
-
- auto spec = unpacker.bucketSpec();
- std::set<std::string> includeFields{kUserDefinedTimeName.toString(), "bye"};
- spec.setFieldSet(includeFields);
- spec.setBehavior(BucketSpec::Behavior::kInclude);
-
- // This calls eraseUnneededComputedMetaProjFields().
- unpacker.setBucketSpec(std::move(spec));
- computedMetaProjFields = unpacker.bucketSpec().computedMetaProjFields();
- // As "hello" was not in the includes, it should be removed.
- ASSERT_FALSE(computedMetaProjFieldsContainsField(computedMetaProjFields, "hello"));
- // As "bye" was in the includes, it should still be in '_computedMetaProjFields'.
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "bye"));
-}
-
-TEST_F(BucketUnpackerTest, EraseUnneededComputedMetaProjFieldsWithExclusiveProject) {
- auto bucket = fromjson(R"(
-{
- control: {version: 1},
- data: {
- _id: {'0':4, '1':5, '2':6},
- time: {'0':4, '1': 5, '2': 6}
- }
-})");
- std::set<std::string> unpackerFields{kUserDefinedTimeName.toString()};
- auto unpacker = makeBucketUnpacker(unpackerFields,
- BucketSpec::Behavior::kInclude,
- std::move(bucket),
- kUserDefinedMetaName.toString());
-
- // Add fields to '_computedMetaProjFields'.
- unpacker.addComputedMetaProjFields({"hello"_sd, "bye"_sd});
- auto computedMetaProjFields = unpacker.bucketSpec().computedMetaProjFields();
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "hello"));
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "bye"));
-
- auto spec = unpacker.bucketSpec();
- std::set<std::string> excludeFields{kUserDefinedTimeName.toString(), "bye"};
- spec.setFieldSet(excludeFields);
- spec.setBehavior(BucketSpec::Behavior::kExclude);
-
- // This calls eraseUnneededComputedMetaProjFields().
- unpacker.setBucketSpec(std::move(spec));
- computedMetaProjFields = unpacker.bucketSpec().computedMetaProjFields();
- // As "hello" was not excluded, it should still exist.
- ASSERT_TRUE(computedMetaProjFieldsContainsField(computedMetaProjFields, "hello"));
- // As "bye" was in the excludes, it should be removed from '_computedMetaProjFields'.
- ASSERT_FALSE(computedMetaProjFieldsContainsField(computedMetaProjFields, "bye"));
-}
-
TEST_F(BucketUnpackerTest, DetermineIncludeTimeField) {
auto bucket = fromjson(R"(
{
@@ -745,25 +665,21 @@ TEST_F(BucketUnpackerTest, DetermineIncludeTimeField) {
})");
std::set<std::string> unpackerFields{kUserDefinedTimeName.toString()};
auto unpacker = makeBucketUnpacker(unpackerFields,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(bucket),
kUserDefinedMetaName.toString());
std::set<std::string> includeFields{kUserDefinedTimeName.toString()};
- auto includeSpec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(includeFields),
- BucketSpec::Behavior::kInclude};
+ auto includeSpec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(includeFields)};
// This calls determineIncludeTimeField.
- unpacker.setBucketSpec(std::move(includeSpec));
+ unpacker.setBucketSpecAndBehavior(std::move(includeSpec), BucketUnpacker::Behavior::kInclude);
ASSERT_TRUE(unpacker.includeTimeField());
std::set<std::string> excludeFields{kUserDefinedTimeName.toString()};
- auto excludeSpec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(excludeFields),
- BucketSpec::Behavior::kExclude};
- unpacker.setBucketSpec(std::move(excludeSpec));
+ auto excludeSpec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(excludeFields)};
+ unpacker.setBucketSpecAndBehavior(std::move(excludeSpec), BucketUnpacker::Behavior::kExclude);
ASSERT_FALSE(unpacker.includeTimeField());
}
@@ -778,26 +694,24 @@ TEST_F(BucketUnpackerTest, DetermineIncludeFieldIncludeMode) {
{"data", Document{}}}
.toBson();
- auto spec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(fields),
- BucketSpec::Behavior::kInclude};
+ auto spec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
BucketUnpacker includeUnpacker;
- includeUnpacker.setBucketSpec(std::move(spec));
+ includeUnpacker.setBucketSpecAndBehavior(std::move(spec), BucketUnpacker::Behavior::kInclude);
// Need to call reset so that the private method calculateFieldsToIncludeExcludeDuringUnpack()
// is called, and _unpackFieldsToIncludeExclude gets filled with fields.
includeUnpacker.reset(std::move(bucket));
// Now the spec knows which fields to include/exclude.
ASSERT_TRUE(determineIncludeField(kUserDefinedTimeName,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
ASSERT_TRUE(determineIncludeField(includedMeasurementField,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
ASSERT_FALSE(determineIncludeField(excludedMeasurementField,
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
}
@@ -812,23 +726,21 @@ TEST_F(BucketUnpackerTest, DetermineIncludeFieldExcludeMode) {
{"data", Document{}}}
.toBson();
- auto spec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(fields),
- BucketSpec::Behavior::kExclude};
+ auto spec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
BucketUnpacker excludeUnpacker;
- excludeUnpacker.setBucketSpec(std::move(spec));
+ excludeUnpacker.setBucketSpecAndBehavior(std::move(spec), BucketUnpacker::Behavior::kExclude);
excludeUnpacker.reset(std::move(bucket));
ASSERT_FALSE(determineIncludeField(kUserDefinedTimeName,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
ASSERT_FALSE(determineIncludeField(includedMeasurementField,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
ASSERT_TRUE(determineIncludeField(excludedMeasurementField,
- BucketSpec::Behavior::kExclude,
+ BucketUnpacker::Behavior::kExclude,
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
}
@@ -846,11 +758,9 @@ auto expectedTimestampObjSize(int32_t rowKeyOffset, int32_t n) {
TEST_F(BucketUnpackerTest, ExtractSingleMeasurement) {
std::set<std::string> fields{
"_id", kUserDefinedMetaName.toString(), kUserDefinedTimeName.toString(), "a", "b"};
- auto spec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(fields),
- BucketSpec::Behavior::kInclude};
- auto unpacker = BucketUnpacker{std::move(spec)};
+ auto spec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
+ auto unpacker = BucketUnpacker{std::move(spec), BucketUnpacker::Behavior::kInclude};
auto d1 = dateFromISOString("2020-02-17T00:00:00.000Z").getValue();
auto d2 = dateFromISOString("2020-02-17T01:00:00.000Z").getValue();
@@ -893,11 +803,9 @@ TEST_F(BucketUnpackerTest, ExtractSingleMeasurement) {
TEST_F(BucketUnpackerTest, ExtractSingleMeasurementSparse) {
std::set<std::string> fields{
"_id", kUserDefinedMetaName.toString(), kUserDefinedTimeName.toString(), "a", "b"};
- auto spec = BucketSpec{kUserDefinedTimeName.toString(),
- kUserDefinedMetaName.toString(),
- std::move(fields),
- BucketSpec::Behavior::kInclude};
- auto unpacker = BucketUnpacker{std::move(spec)};
+ auto spec = BucketSpec{
+ kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
+ auto unpacker = BucketUnpacker{std::move(spec), BucketUnpacker::Behavior::kInclude};
auto d1 = dateFromISOString("2020-02-17T00:00:00.000Z").getValue();
auto d2 = dateFromISOString("2020-02-17T01:00:00.000Z").getValue();
@@ -984,7 +892,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountLess) {
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, -1);
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(modifiedCompressedBucket),
kUserDefinedMetaName.toString());
@@ -1019,7 +927,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountMore) {
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, 1);
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(modifiedCompressedBucket),
kUserDefinedMetaName.toString());
@@ -1054,7 +962,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountMissing) {
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, 0);
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(modifiedCompressedBucket),
kUserDefinedMetaName.toString());
@@ -1091,7 +999,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedElementMismatchDataField) {
modifyCompressedBucketRemoveLastInField(*compressedBucket, "a"_sd);
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(modifiedCompressedBucket),
kUserDefinedMetaName.toString());
@@ -1126,7 +1034,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedElementMismatchTimeField) {
modifyCompressedBucketRemoveLastInField(*compressedBucket, "time"_sd);
auto unpacker = makeBucketUnpacker(std::move(fields),
- BucketSpec::Behavior::kInclude,
+ BucketUnpacker::Behavior::kInclude,
std::move(modifiedCompressedBucket),
kUserDefinedMetaName.toString());
diff --git a/src/mongo/db/exec/collection_scan.cpp b/src/mongo/db/exec/collection_scan.cpp
index 5898ac224d9..506ce18d1ce 100644
--- a/src/mongo/db/exec/collection_scan.cpp
+++ b/src/mongo/db/exec/collection_scan.cpp
@@ -185,26 +185,9 @@ PlanStage::StageState CollectionScan::doWork(WorkingSetID* out) {
<< "attempting to resume no longer exists in the collection. "
<< "recordId: " << recordIdToSeek);
}
-
- if (_params.resumeAfterRecordId && !_params.resumeAfterRecordId->isNull()) {
- invariant(!_params.tailable);
- invariant(_lastSeenId.isNull());
- // Seek to where we are trying to resume the scan from. Signal a KeyNotFound
- // error if the record no longer exists.
- //
- // Note that we want to return the record *after* this one since we have already
- // returned this one prior to the resume.
- auto& recordIdToSeek = *_params.resumeAfterRecordId;
- if (!_cursor->seekExact(recordIdToSeek)) {
- uasserted(ErrorCodes::KeyNotFound,
- str::stream()
- << "Failed to resume collection scan: the recordId from "
- "which we are "
- << "attempting to resume no longer exists in the collection. "
- << "recordId: " << recordIdToSeek);
- }
- }
}
+
+ return PlanStage::NEED_TIME;
}
if (_lastSeenId.isNull() && _params.direction == CollectionScanParams::FORWARD &&
@@ -371,24 +354,16 @@ PlanStage::StageState CollectionScan::returnIfMatches(WorkingSetMember* member,
// In the future, we could change seekNear() to always return a record after minRecord in the
// direction of the scan. However, tailable scans depend on the current behavior in order to
// mark their position for resuming the tailable scan later on.
- if (beforeStartOfRange(_params, *member)) {
- _workingSet->free(memberID);
- return PlanStage::NEED_TIME;
- }
-
- if (!Filter::passes(member, _filter)) {
- _workingSet->free(memberID);
- if (_params.shouldReturnEofOnFilterMismatch) {
- _commonStats.isEOF = true;
- return PlanStage::IS_EOF;
+ if (!beforeStartOfRange(_params, *member) && Filter::passes(member, _filter)) {
+ if (_params.stopApplyingFilterAfterFirstMatch) {
+ _filter = nullptr;
}
+ *out = memberID;
+ return PlanStage::ADVANCED;
+ } else {
+ _workingSet->free(memberID);
return PlanStage::NEED_TIME;
}
- if (_params.stopApplyingFilterAfterFirstMatch) {
- _filter = nullptr;
- }
- *out = memberID;
- return PlanStage::ADVANCED;
}
bool CollectionScan::isEOF() {
@@ -431,7 +406,9 @@ void CollectionScan::doReattachToOperationContext() {
unique_ptr<PlanStageStats> CollectionScan::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (nullptr != _filter) {
- _commonStats.filter = _filter->serialize();
+ BSONObjBuilder bob;
+ _filter->serialize(&bob);
+ _commonStats.filter = bob.obj();
}
unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_COLLSCAN);
diff --git a/src/mongo/db/exec/collection_scan_common.h b/src/mongo/db/exec/collection_scan_common.h
index 8aecb6750fc..ba5559a4491 100644
--- a/src/mongo/db/exec/collection_scan_common.h
+++ b/src/mongo/db/exec/collection_scan_common.h
@@ -42,16 +42,12 @@ struct CollectionScanParams {
};
enum class ScanBoundInclusion {
- kExcludeBothStartAndEndRecords = 0b00,
- kIncludeStartRecordOnly = 0b01,
- kIncludeEndRecordOnly = 0b10,
- kIncludeBothStartAndEndRecords = 0b11,
+ kExcludeBothStartAndEndRecords,
+ kIncludeStartRecordOnly,
+ kIncludeEndRecordOnly,
+ kIncludeBothStartAndEndRecords,
};
- static ScanBoundInclusion makeInclusion(bool startInclusive, bool endInclusive) {
- return ScanBoundInclusion(int(startInclusive) | (int(endInclusive) << 1));
- }
-
// If present, this parameter sets the start point of a forward scan or the end point of a
// reverse scan. A forward scan will start scanning at the document with the lowest RecordId
// greater than or equal to minRecord. A reverse scan will stop and return EOF on the first
@@ -115,10 +111,6 @@ struct CollectionScanParams {
// Whether or not to wait for oplog visibility on oplog collection scans.
bool shouldWaitForOplogVisibility = false;
-
- // Whether or not to return EOF and stop further scanning once MatchExpression evaluates to
- // false. Can only be set to true if the MatchExpression is present.
- bool shouldReturnEofOnFilterMismatch = false;
};
} // namespace mongo
diff --git a/src/mongo/db/exec/delete_stage.cpp b/src/mongo/db/exec/delete_stage.cpp
index 581ef2ef294..83941b91c4e 100644
--- a/src/mongo/db/exec/delete_stage.cpp
+++ b/src/mongo/db/exec/delete_stage.cpp
@@ -178,38 +178,23 @@ PlanStage::StageState DeleteStage::doWork(WorkingSetID* out) {
bool writeToOrphan = false;
if (!_params->isExplain && !_params->fromMigrate) {
- try {
- const auto action = _preWriteFilter.computeAction(member->doc.value());
- if (action == write_stage_common::PreWriteFilter::Action::kSkip) {
- LOGV2_DEBUG(
- 5983201,
- 3,
- "Skipping delete operation to orphan document to prevent a wrong change "
- "stream event",
- "namespace"_attr = collection()->ns(),
- "record"_attr = member->doc.value());
- return PlanStage::NEED_TIME;
- } else if (action == write_stage_common::PreWriteFilter::Action::kWriteAsFromMigrate) {
- LOGV2_DEBUG(6184700,
- 3,
- "Marking delete operation to orphan document with the fromMigrate flag "
- "to prevent a wrong change stream event",
- "namespace"_attr = collection()->ns(),
- "record"_attr = member->doc.value());
- writeToOrphan = true;
- }
- } catch (const ExceptionFor<ErrorCodes::StaleConfig>& ex) {
- if (ex->getVersionReceived() == ChunkVersion::IGNORED() &&
- ex->getCriticalSectionSignal()) {
- // If ChunkVersion is IGNORED and we encountered a critical section, then yield,
- // wait for the critical section to finish and then we'll resume the write from the
- // point we had left. We do this to prevent large multi-writes from repeatedly
- // failing due to StaleConfig and exhausting the mongos retry attempts.
- planExecutorShardingCriticalSectionFuture(opCtx()) = ex->getCriticalSectionSignal();
- memberFreer.dismiss(); // Keep this member around so we can retry deleting it.
- return prepareToRetryWSM(id, out);
- }
- throw;
+ const auto action = _preWriteFilter.computeAction(member->doc.value());
+ if (action == write_stage_common::PreWriteFilter::Action::kSkip) {
+ LOGV2_DEBUG(5983201,
+ 3,
+ "Skipping delete operation to orphan document to prevent a wrong change "
+ "stream event",
+ "namespace"_attr = collection()->ns(),
+ "record"_attr = member->doc.value());
+ return PlanStage::NEED_TIME;
+ } else if (action == write_stage_common::PreWriteFilter::Action::kWriteAsFromMigrate) {
+ LOGV2_DEBUG(6184700,
+ 3,
+ "Marking delete operation to orphan document with the fromMigrate flag "
+ "to prevent a wrong change stream event",
+ "namespace"_attr = collection()->ns(),
+ "record"_attr = member->doc.value());
+ writeToOrphan = true;
}
}
@@ -250,18 +235,6 @@ PlanStage::StageState DeleteStage::doWork(WorkingSetID* out) {
} catch (const WriteConflictException&) {
memberFreer.dismiss(); // Keep this member around so we can retry deleting it.
return prepareToRetryWSM(id, out);
- } catch (const ExceptionFor<ErrorCodes::StaleConfig>& ex) {
- if (ex->getVersionReceived() == ChunkVersion::IGNORED() &&
- ex->getCriticalSectionSignal()) {
- // If ChunkVersion is IGNORED and we encountered a critical section, then yield,
- // wait for the critical section to finish and then we'll resume the write from the
- // point we had left. We do this to prevent large multi-writes from repeatedly
- // failing due to StaleConfig and exhausting the mongos retry attempts.
- planExecutorShardingCriticalSectionFuture(opCtx()) = ex->getCriticalSectionSignal();
- memberFreer.dismiss(); // Keep this member around so we can retry deleting it.
- return prepareToRetryWSM(id, out);
- }
- throw;
}
}
_specificStats.docsDeleted += _params->numStatsForDoc ? _params->numStatsForDoc(bsonObjDoc) : 1;
diff --git a/src/mongo/db/exec/document_value/document.cpp b/src/mongo/db/exec/document_value/document.cpp
index 7172370e4c3..dd833b3c28f 100644
--- a/src/mongo/db/exec/document_value/document.cpp
+++ b/src/mongo/db/exec/document_value/document.cpp
@@ -88,11 +88,8 @@ const StringDataSet Document::allMetadataFieldNames{Document::metaFieldTextScore
Document::metaFieldGeoNearPoint,
Document::metaFieldSearchScore,
Document::metaFieldSearchHighlights,
- Document::metaFieldSearchSortValues,
Document::metaFieldIndexKey,
- Document::metaFieldSearchScoreDetails,
- Document::metaFieldVectorSearchScore,
- Document::metaFieldSearchSequenceToken};
+ Document::metaFieldSearchScoreDetails};
DocumentStorageIterator::DocumentStorageIterator(DocumentStorage* storage, BSONObjIterator bsonIt)
: _bsonIt(std::move(bsonIt)),
@@ -133,7 +130,7 @@ bool DocumentStorageIterator::shouldSkipDeleted() {
// If we strip the metadata see if a field name matches the known list. All metadata fields
// start with '$' so optimize for a quick bailout.
- if (_storage->bsonHasMetadata() && !fieldName.empty() && fieldName[0] == '$' &&
+ if (_storage->stripMetadata() && fieldName[0] == '$' &&
Document::allMetadataFieldNames.contains(fieldName)) {
return true;
}
@@ -346,8 +343,8 @@ void DocumentStorage::reserveFields(size_t expectedFields) {
}
intrusive_ptr<DocumentStorage> DocumentStorage::clone() const {
- auto out = make_intrusive<DocumentStorage>(
- _bson, _bsonHasMetadata, _modified, _numBytesFromBSONInCache);
+ auto out =
+ make_intrusive<DocumentStorage>(_bson, _stripMetadata, _modified, _numBytesFromBSONInCache);
if (_cache) {
// Make a copy of the buffer with the fields.
@@ -376,7 +373,6 @@ intrusive_ptr<DocumentStorage> DocumentStorage::clone() const {
out->_haveLazyLoadedMetadata = _haveLazyLoadedMetadata;
out->_metadataFields = _metadataFields;
- out->_snapshottedSize = _snapshottedSize;
return out;
}
@@ -393,12 +389,11 @@ DocumentStorage::~DocumentStorage() {
}
}
-void DocumentStorage::reset(const BSONObj& bson, bool bsonHasMetadata) {
+void DocumentStorage::reset(const BSONObj& bson, bool stripMetadata) {
_bson = bson;
_numBytesFromBSONInCache = 0;
- _bsonHasMetadata = bsonHasMetadata;
+ _stripMetadata = stripMetadata;
_modified = false;
- _snapshottedSize = 0;
// Clean cache.
for (auto it = iteratorCacheOnly(); !it.atEnd(); it.advance()) {
@@ -414,22 +409,10 @@ void DocumentStorage::reset(const BSONObj& bson, bool bsonHasMetadata) {
_metadataFields = DocumentMetadataFields{};
}
-Document DocumentStorage::shred() const {
- MutableDocument md;
- // Iterate raw bson if possible. This avoids caching all of the values in a doc that might get
- // thrown away.
- if (!isModified() && !bsonHasMetadata()) {
- for (const auto& elem : _bson) {
- md[elem.fieldNameStringData()] = Value(elem).shred();
- }
- } else {
- for (DocumentStorageIterator it = iterator(); !it.atEnd(); it.advance()) {
- const auto& valueElem = it.get();
- md[it.fieldName()] = valueElem.val.shred();
- }
+void DocumentStorage::fillCache() const {
+ for (DocumentStorageIterator it = iterator(); !it.atEnd(); it.advance()) {
+ it->val.fillCache();
}
- md.setMetadata(DocumentMetadataFields(metadata()));
- return md.freeze();
}
void DocumentStorage::loadLazyMetadata() const {
@@ -437,13 +420,11 @@ void DocumentStorage::loadLazyMetadata() const {
return;
}
- bool oldModified = _metadataFields.isModified();
-
BSONObjIterator it(_bson);
while (it.more()) {
BSONElement elem(it.next());
auto fieldName = elem.fieldNameStringData();
- if (!fieldName.empty() && fieldName[0] == '$') {
+ if (fieldName[0] == '$') {
if (fieldName == Document::metaFieldTextScore) {
_metadataFields.setTextScore(elem.Double());
} else if (fieldName == Document::metaFieldSearchScore) {
@@ -479,17 +460,10 @@ void DocumentStorage::loadLazyMetadata() const {
_metadataFields.setIndexKey(elem.Obj());
} else if (fieldName == Document::metaFieldSearchScoreDetails) {
_metadataFields.setSearchScoreDetails(elem.Obj());
- } else if (fieldName == Document::metaFieldSearchSortValues) {
- _metadataFields.setSearchSortValues(elem.Obj());
- } else if (fieldName == Document::metaFieldVectorSearchScore) {
- _metadataFields.setVectorSearchScore(elem.Double());
- } else if (fieldName == Document::metaFieldSearchSequenceToken) {
- _metadataFields.setSearchSequenceToken(Value(elem));
}
}
}
- _metadataFields.setModified(oldModified);
_haveLazyLoadedMetadata = true;
}
@@ -539,11 +513,22 @@ void Document::toBson(BSONObjBuilder* builder, size_t recursionLevel) const {
}
}
+BSONObj Document::toBson() const {
+ if (!storage().isModified() && !storage().stripMetadata()) {
+ return storage().bsonObj();
+ }
+
+ BSONObjBuilder bb;
+ toBson(&bb);
+ return bb.obj();
+}
+
boost::optional<BSONObj> Document::toBsonIfTriviallyConvertible() const {
- if (isTriviallyConvertible()) {
+ if (!storage().isModified() && !storage().stripMetadata()) {
return storage().bsonObj();
+ } else {
+ return boost::none;
}
- return boost::none;
}
constexpr StringData Document::metaFieldTextScore;
@@ -554,44 +539,35 @@ constexpr StringData Document::metaFieldGeoNearPoint;
constexpr StringData Document::metaFieldSearchScore;
constexpr StringData Document::metaFieldSearchHighlights;
constexpr StringData Document::metaFieldSearchScoreDetails;
-constexpr StringData Document::metaFieldSearchSortValues;
-constexpr StringData Document::metaFieldVectorSearchScore;
-void Document::toBsonWithMetaData(BSONObjBuilder* builder) const {
- toBson(builder);
+BSONObj Document::toBsonWithMetaData() const {
+ BSONObjBuilder bb;
+ toBson(&bb);
if (!metadata()) {
- return;
+ return bb.obj();
}
if (metadata().hasTextScore())
- builder->append(metaFieldTextScore, metadata().getTextScore());
+ bb.append(metaFieldTextScore, metadata().getTextScore());
if (metadata().hasRandVal())
- builder->append(metaFieldRandVal, metadata().getRandVal());
+ bb.append(metaFieldRandVal, metadata().getRandVal());
if (metadata().hasSortKey())
- builder->append(metaFieldSortKey,
- DocumentMetadataFields::serializeSortKey(metadata().isSingleElementKey(),
- metadata().getSortKey()));
+ bb.append(metaFieldSortKey,
+ DocumentMetadataFields::serializeSortKey(metadata().isSingleElementKey(),
+ metadata().getSortKey()));
if (metadata().hasGeoNearDistance())
- builder->append(metaFieldGeoNearDistance, metadata().getGeoNearDistance());
+ bb.append(metaFieldGeoNearDistance, metadata().getGeoNearDistance());
if (metadata().hasGeoNearPoint())
- metadata().getGeoNearPoint().addToBsonObj(builder, metaFieldGeoNearPoint);
+ metadata().getGeoNearPoint().addToBsonObj(&bb, metaFieldGeoNearPoint);
if (metadata().hasSearchScore())
- builder->append(metaFieldSearchScore, metadata().getSearchScore());
+ bb.append(metaFieldSearchScore, metadata().getSearchScore());
if (metadata().hasSearchHighlights())
- metadata().getSearchHighlights().addToBsonObj(builder, metaFieldSearchHighlights);
+ metadata().getSearchHighlights().addToBsonObj(&bb, metaFieldSearchHighlights);
if (metadata().hasIndexKey())
- builder->append(metaFieldIndexKey, metadata().getIndexKey());
+ bb.append(metaFieldIndexKey, metadata().getIndexKey());
if (metadata().hasSearchScoreDetails())
- builder->append(metaFieldSearchScoreDetails, metadata().getSearchScoreDetails());
- if (metadata().hasSearchSortValues()) {
- builder->append(metaFieldSearchSortValues, metadata().getSearchSortValues());
- }
- if (metadata().hasSearchSequenceToken()) {
- metadata().getSearchSequenceToken().addToBsonObj(builder, metaFieldSearchSequenceToken);
- }
- if (metadata().hasVectorSearchScore()) {
- builder->append(metaFieldVectorSearchScore, metadata().getVectorSearchScore());
- }
+ bb.append(metaFieldSearchScoreDetails, metadata().getSearchScoreDetails());
+ return bb.obj();
}
Document Document::fromBsonWithMetaData(const BSONObj& bson) {
@@ -728,17 +704,31 @@ const Value Document::getNestedField(const FieldPath& path, vector<Position>* po
return getNestedFieldHelper(*this, path, positions, 0);
}
-size_t Document::getApproximateSize() const {
- return sizeof(Document) + storage().snapshottedApproximateSize();
+size_t Document::getApproximateSizeWithoutBackingBSON() const {
+ size_t size = sizeof(Document);
+ if (!_storage)
+ return size;
+
+ size += sizeof(DocumentStorage);
+ size += storage().allocatedBytes();
+
+ for (auto it = storage().iteratorCacheOnly(); !it.atEnd(); it.advance()) {
+ size += it->val.getApproximateSize();
+ size -= sizeof(Value); // already accounted for above
+ }
+
+ // The metadata also occupies space in the document storage that's pre-allocated.
+ size += storage().getMetadataApproximateSize();
+
+ return size;
}
-size_t Document::getCurrentApproximateSize() const {
- return sizeof(Document) + storage().currentApproximateSize();
+size_t Document::getApproximateSize() const {
+ return getApproximateSizeWithoutBackingBSON() + storage().bsonObjSize();
}
size_t Document::memUsageForSorter() const {
- return storage().currentApproximateSize() - storage().bsonObjSize() +
- storage().nonCachedBsonObjSize();
+ return getApproximateSizeWithoutBackingBSON() + storage().nonCachedBsonObjSize();
}
void Document::hash_combine(size_t& seed,
diff --git a/src/mongo/db/exec/document_value/document.h b/src/mongo/db/exec/document_value/document.h
index 6114aee792d..8fcfb28dd8b 100644
--- a/src/mongo/db/exec/document_value/document.h
+++ b/src/mongo/db/exec/document_value/document.h
@@ -100,10 +100,7 @@ public:
static constexpr StringData metaFieldSearchScore = "$searchScore"_sd;
static constexpr StringData metaFieldSearchHighlights = "$searchHighlights"_sd;
static constexpr StringData metaFieldSearchScoreDetails = "$searchScoreDetails"_sd;
- static constexpr StringData metaFieldSearchSortValues = "$searchSortValues"_sd;
static constexpr StringData metaFieldIndexKey = "$indexKey"_sd;
- static constexpr StringData metaFieldVectorSearchScore = "$vectorSearchScore"_sd;
- static constexpr StringData metaFieldSearchSequenceToken = "$searchSequenceToken"_sd;
static const StringDataSet allMetadataFieldNames;
@@ -194,8 +191,7 @@ public:
/**
* Get the approximate size of the Document, plus its underlying storage and sub-values. Returns
- * size in bytes. The return value of this function is snapshotted. All subsequent calls of this
- * method will return the same value.
+ * size in bytes.
*
* Note: Some memory may be shared with other Documents or between fields within a single
* Document so this can overestimate usage.
@@ -206,11 +202,6 @@ public:
size_t getApproximateSize() const;
/**
- * Same as 'getApproximateSize()', but this method re-computes the size on every call.
- */
- size_t getCurrentApproximateSize() const;
-
- /**
* Return the approximate amount of space used by metadata.
*/
size_t getMetadataApproximateSize() const {
@@ -248,10 +239,10 @@ public:
}
/**
- * Returns a cache-only copy of the document with no backing bson.
+ * Populates the internal cache by recursively walking the underlying BSON.
*/
- Document shred() const {
- return storage().shred();
+ void fillCache() const {
+ storage().fillCache();
}
/** Calculate a hash value.
@@ -262,38 +253,12 @@ public:
void hash_combine(size_t& seed, const StringData::ComparatorInterface* stringComparator) const;
/**
- * Returns true, if this document is trivially convertible to BSON, meaning the underlying
- * storage is already in BSON format and there are no damages.
- */
- bool isTriviallyConvertible() const {
- return !storage().isModified() && !storage().bsonHasMetadata();
- }
-
- /**
- * Returns true, if this document is trivially convertible to BSON with metadata, meaning the
- * underlying storage is already in BSON format and there are no damages.
- */
- bool isTriviallyConvertibleWithMetadata() const {
- return !storage().isModified() && !storage().isMetadataModified();
- }
-
- /**
* Serializes this document to the BSONObj under construction in 'builder'. Metadata is not
* included. Throws a AssertionException if 'recursionLevel' exceeds the maximum allowable
* depth.
*/
void toBson(BSONObjBuilder* builder, size_t recursionLevel = 1) const;
-
- template <typename BSONTraits = BSONObj::DefaultSizeTrait>
- BSONObj toBson() const {
- if (isTriviallyConvertible()) {
- return storage().bsonObj();
- }
-
- BSONObjBuilder bb;
- toBson(&bb);
- return bb.obj<BSONTraits>();
- }
+ BSONObj toBson() const;
/**
* Serializes this document iff the conversion is "trivial," meaning that the underlying storage
@@ -307,18 +272,7 @@ public:
/**
* Like the 'toBson()' method, but includes metadata as top-level fields.
*/
- void toBsonWithMetaData(BSONObjBuilder* builder) const;
-
- template <typename BSONTraits = BSONObj::DefaultSizeTrait>
- BSONObj toBsonWithMetaData() const {
- if (isTriviallyConvertibleWithMetadata()) {
- return storage().bsonObj();
- }
-
- BSONObjBuilder bb;
- toBsonWithMetaData(&bb);
- return bb.obj<BSONTraits>();
- }
+ BSONObj toBsonWithMetaData() const;
/**
* Like Document(BSONObj) but treats top-level fields with special names as metadata.
@@ -416,6 +370,12 @@ private:
getNestedFieldNonCachingHelper(const FieldPath& dottedField, size_t level) const;
boost::intrusive_ptr<const DocumentStorage> _storage;
+
+ /**
+ * Returns the approximate size of this `Document` instance without considering the size of its
+ * backing BSON object.
+ */
+ size_t getApproximateSizeWithoutBackingBSON() const;
};
//
@@ -551,11 +511,13 @@ public:
}
/**
- * Replace the current base Document with the BSON object. Setting 'bsonHasMetadata' to true
- * signals that the BSON object contains metadata fields.
+ * Replace the current base Document with bson.
+ *
+ * The paramater 'stripMetadata' controls whether we strip the metadata fields from the
+ * underlying bson when converting the document object back to bson.
*/
- void reset(const BSONObj& bson, bool bsonHasMetadata) {
- storage().reset(bson, bsonHasMetadata);
+ void reset(const BSONObj& bson, bool stripMetadata) {
+ storage().reset(bson, stripMetadata);
}
/** Add the given field to the Document.
@@ -692,7 +654,6 @@ public:
* TODO: there are some optimizations that may make sense at freeze time.
*/
Document freeze() {
- resetSnapshottedApproximateSize();
// This essentially moves _storage into a new Document by way of temp.
Document ret;
boost::intrusive_ptr<const DocumentStorage> temp(storagePtr(), /*inc_ref_count=*/false);
@@ -711,12 +672,8 @@ public:
* Note that unlike freeze(), this indicates intention to continue
* modifying this document. The returned Document will not observe
* future changes to this MutableDocument.
- *
- * Note that the computed snapshotted approximate size of the Document
- * is not preserved across calls.
*/
Document peek() {
- resetSnapshottedApproximateSize();
return Document(storagePtr());
}
@@ -738,13 +695,13 @@ public:
storage().makeOwned();
}
- /**
- * Creates a new document storage with the BSON object. Setting 'bsonHasMetadata' to true
- * signals that the BSON object contains metadata fields (the complete list is in
- * Document::allMetadataFieldNames).
+ /** Create a new document storage with the BSON object.
+ *
+ * The optional paramater 'stripMetadata' controls whether we strip the metadata fields (the
+ * complete list is in Document::allMetadataFieldNames).
*/
- DocumentStorage& newStorageWithBson(const BSONObj& bson, bool bsonHasMetadata) {
- reset(make_intrusive<DocumentStorage>(bson, bsonHasMetadata, false, 0));
+ DocumentStorage& newStorageWithBson(const BSONObj& bson, bool stripMetadata) {
+ reset(make_intrusive<DocumentStorage>(bson, stripMetadata, false, 0));
return const_cast<DocumentStorage&>(*storagePtr());
}
@@ -782,19 +739,12 @@ private:
MutableValue getNestedFieldHelper(const FieldPath& dottedField, size_t level);
MutableValue getNestedFieldHelper(const std::vector<Position>& positions, size_t level);
- // this should only be called by storage methods and peek/freeze/resetsnapshottedApproximateSize
+ // this should only be called by storage methods and peek/freeze
const DocumentStorage* storagePtr() const {
dassert(!_storage || typeid(*_storage) == typeid(const DocumentStorage));
return static_cast<const DocumentStorage*>(_storage);
}
- void resetSnapshottedApproximateSize() {
- auto mutableStorage = const_cast<DocumentStorage*>(storagePtr());
- if (mutableStorage) {
- mutableStorage->resetSnapshottedApproximateSize();
- }
- }
-
// These are both const to prevent modifications bypassing storage() method.
// They always point to NULL or an object with dynamic type DocumentStorage.
const RefCountable* _storageHolder; // Only used in constructors and destructor
diff --git a/src/mongo/db/exec/document_value/document_internal.h b/src/mongo/db/exec/document_value/document_internal.h
index d7fb3b337d6..1387ca908b4 100644
--- a/src/mongo/db/exec/document_value/document_internal.h
+++ b/src/mongo/db/exec/document_value/document_internal.h
@@ -359,11 +359,11 @@ public:
/**
* Construct a storage from the BSON. The BSON is lazily processed as fields are requested from
- * the document. If we know that the BSON contains metadata fields we can set the
- * 'bsonHasMetadata' flag to true.
+ * the document. If we know that the BSON does not contain any metadata fields we can set the
+ * 'stripMetadata' flag to false that will speed up the field iteration.
*/
DocumentStorage(const BSONObj& bson,
- bool bsonHasMetadata,
+ bool stripMetadata,
bool modified,
uint32_t numBytesFromBSONInCache)
: _cache(nullptr),
@@ -373,17 +373,17 @@ public:
_hashTabMask(0),
_bson(bson),
_numBytesFromBSONInCache(numBytesFromBSONInCache),
- _bsonHasMetadata(bsonHasMetadata),
+ _stripMetadata(stripMetadata),
_modified(modified) {}
~DocumentStorage();
- void reset(const BSONObj& bson, bool bsonHasMetadata);
+ void reset(const BSONObj& bson, bool stripMetadata);
/**
- * Returns a cache-only copy of the document with no backing bson.
+ * Populates the cache by recursively walking the underlying BSON.
*/
- Document shred() const;
+ void fillCache() const;
static const DocumentStorage& emptyDoc() {
return kEmptyDoc;
@@ -551,7 +551,7 @@ public:
* WorkingSetMember.
*/
const DocumentMetadataFields& metadata() const {
- if (_bsonHasMetadata) {
+ if (_stripMetadata) {
loadLazyMetadata();
}
return _metadataFields;
@@ -589,8 +589,8 @@ public:
return _firstElement ? _firstElement->plusBytes(_usedBytes) : nullptr;
}
- auto bsonHasMetadata() const {
- return _bsonHasMetadata;
+ auto stripMetadata() const {
+ return _stripMetadata;
}
Position constructInCache(const BSONElement& elem);
@@ -599,37 +599,10 @@ public:
return _modified;
}
- auto isMetadataModified() const {
- return _metadataFields.isModified();
- }
-
auto bsonObj() const {
return _bson;
}
- size_t currentApproximateSize() const {
- size_t size = sizeof(DocumentStorage) + allocatedBytes() + getMetadataApproximateSize() +
- bsonObjSize();
-
- for (auto it = iteratorCacheOnly(); !it.atEnd(); it.advance()) {
- size += it->val.getApproximateSize() - sizeof(Value);
- }
-
- return size;
- }
-
- size_t snapshottedApproximateSize() const {
- if (_snapshottedSize == 0) {
- const_cast<DocumentStorage*>(this)->_snapshottedSize = currentApproximateSize();
- }
-
- return _snapshottedSize;
- }
-
- void resetSnapshottedApproximateSize() {
- _snapshottedSize = 0;
- }
-
private:
/// Returns the position of the named field in the cache or Position()
template <typename T>
@@ -712,23 +685,22 @@ private:
// whole backing BSON, but only the portion of backing BSON that's not already in the cache.
uint32_t _numBytesFromBSONInCache = 0;
- // Tracks whether or not the metadata has been lazy-loaded from the backing '_bson' object. If
- // so, then no attempt will be made to load the metadata again, even if the metadata has been
- // released by a call to 'releaseMetadata()'.
+ // If '_stripMetadata' is true, tracks whether or not the metadata has been lazy-loaded from the
+ // backing '_bson' object. If so, then no attempt will be made to load the metadata again, even
+ // if the metadata has been released by a call to 'releaseMetadata()'.
mutable bool _haveLazyLoadedMetadata = false;
mutable DocumentMetadataFields _metadataFields;
- // True if this storage was constructed from BSON with metadata. Serializing this object using
- // the 'toBson()' method will omit (strip) the metadata fields.
- bool _bsonHasMetadata{false};
+ // The storage constructed from a BSON value may contain metadata. When we process the BSON we
+ // have to move the metadata to the MetadataFields object. If we know that the BSON does not
+ // have any metadata we can set _stripMetadata to false that will speed up the iteration.
+ bool _stripMetadata{false};
// This flag is set to true anytime the storage returns a mutable field. It is used to optimize
// a conversion to BSON; i.e. if there are not any modifications we can directly return _bson.
bool _modified{false};
- size_t _snapshottedSize{0};
-
// Defined in document.cpp
static const DocumentStorage kEmptyDoc;
diff --git a/src/mongo/db/exec/document_value/document_metadata_fields.cpp b/src/mongo/db/exec/document_value/document_metadata_fields.cpp
index 27f64ef7286..90e15ef5c2a 100644
--- a/src/mongo/db/exec/document_value/document_metadata_fields.cpp
+++ b/src/mongo/db/exec/document_value/document_metadata_fields.cpp
@@ -46,7 +46,6 @@ DocumentMetadataFields::DocumentMetadataFields(const DocumentMetadataFields& oth
DocumentMetadataFields& DocumentMetadataFields::operator=(const DocumentMetadataFields& other) {
_holder = other._holder ? std::make_unique<MetadataHolder>(*other._holder) : nullptr;
- _modified = true;
return *this;
}
@@ -55,7 +54,6 @@ DocumentMetadataFields::DocumentMetadataFields(DocumentMetadataFields&& other)
DocumentMetadataFields& DocumentMetadataFields::operator=(DocumentMetadataFields&& other) {
_holder = std::move(other._holder);
- _modified = true;
return *this;
}
@@ -87,21 +85,12 @@ void DocumentMetadataFields::mergeWith(const DocumentMetadataFields& other) {
if (!hasSearchScoreDetails() && other.hasSearchScoreDetails()) {
setSearchScoreDetails(other.getSearchScoreDetails());
}
- if (!hasSearchSequenceToken() && other.hasSearchSequenceToken()) {
- setSearchSequenceToken(other.getSearchSequenceToken());
- }
if (!hasTimeseriesBucketMinTime() && other.hasTimeseriesBucketMinTime()) {
setTimeseriesBucketMinTime(other.getTimeseriesBucketMinTime());
}
if (!hasTimeseriesBucketMaxTime() && other.hasTimeseriesBucketMaxTime()) {
setTimeseriesBucketMaxTime(other.getTimeseriesBucketMaxTime());
}
- if (!hasSearchSortValues() && other.hasSearchSortValues()) {
- setSearchSortValues(other.getSearchSortValues());
- }
- if (!hasVectorSearchScore() && other.hasVectorSearchScore()) {
- setVectorSearchScore(other.getVectorSearchScore());
- }
}
void DocumentMetadataFields::copyFrom(const DocumentMetadataFields& other) {
@@ -132,21 +121,12 @@ void DocumentMetadataFields::copyFrom(const DocumentMetadataFields& other) {
if (other.hasSearchScoreDetails()) {
setSearchScoreDetails(other.getSearchScoreDetails());
}
- if (other.hasSearchSequenceToken()) {
- setSearchSequenceToken(other.getSearchSequenceToken());
- }
if (other.hasTimeseriesBucketMinTime()) {
setTimeseriesBucketMinTime(other.getTimeseriesBucketMinTime());
}
if (other.hasTimeseriesBucketMaxTime()) {
setTimeseriesBucketMaxTime(other.getTimeseriesBucketMaxTime());
}
- if (other.hasSearchSortValues()) {
- setSearchSortValues(other.getSearchSortValues());
- }
- if (other.hasVectorSearchScore()) {
- setVectorSearchScore(other.getVectorSearchScore());
- }
}
size_t DocumentMetadataFields::getApproximateSize() const {
@@ -168,8 +148,7 @@ size_t DocumentMetadataFields::getApproximateSize() const {
size -= sizeof(_holder->searchHighlights);
size += _holder->indexKey.objsize();
size += _holder->searchScoreDetails.objsize();
- size += _holder->searchSortValues.objsize();
- size -= sizeof(_holder->searchSequenceToken);
+
return size;
}
@@ -217,10 +196,6 @@ void DocumentMetadataFields::serializeForSorter(BufBuilder& buf) const {
buf.appendNum(static_cast<char>(MetaType::kSearchScoreDetails + 1));
getSearchScoreDetails().appendSelfToBufBuilder(buf);
}
- if (hasSearchSequenceToken()) {
- buf.appendNum(static_cast<char>(MetaType::kSearchSequenceToken + 1));
- getSearchSequenceToken().serializeForSorter(buf);
- }
if (hasTimeseriesBucketMinTime()) {
buf.appendNum(static_cast<char>(MetaType::kTimeseriesBucketMinTime + 1));
buf.appendNum(getTimeseriesBucketMinTime().toMillisSinceEpoch());
@@ -229,14 +204,6 @@ void DocumentMetadataFields::serializeForSorter(BufBuilder& buf) const {
buf.appendNum(static_cast<char>(MetaType::kTimeseriesBucketMaxTime + 1));
buf.appendNum(getTimeseriesBucketMaxTime().toMillisSinceEpoch());
}
- if (hasSearchSortValues()) {
- buf.appendNum(static_cast<char>(MetaType::kSearchSortValues + 1));
- getSearchSortValues().appendSelfToBufBuilder(buf);
- }
- if (hasVectorSearchScore()) {
- buf.appendNum(static_cast<char>(MetaType::kVectorSearchScore + 1));
- buf.appendNum(getVectorSearchScore());
- }
buf.appendNum(static_cast<char>(0));
}
@@ -269,19 +236,9 @@ void DocumentMetadataFields::deserializeForSorter(BufReader& buf, DocumentMetada
out->setSearchScoreDetails(
BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings()));
} else if (marker == static_cast<char>(MetaType::kTimeseriesBucketMinTime) + 1) {
- out->setTimeseriesBucketMinTime(
- Date_t::fromMillisSinceEpoch(buf.read<LittleEndian<long long>>()));
+ out->setTimeseriesBucketMinTime(Date_t::fromMillisSinceEpoch(buf.read<long long>()));
} else if (marker == static_cast<char>(MetaType::kTimeseriesBucketMaxTime) + 1) {
- out->setTimeseriesBucketMaxTime(
- Date_t::fromMillisSinceEpoch(buf.read<LittleEndian<long long>>()));
- } else if (marker == static_cast<char>(MetaType::kSearchSortValues) + 1) {
- out->setSearchSortValues(
- BSONObj::deserializeForSorter(buf, BSONObj::SorterDeserializeSettings()));
- } else if (marker == static_cast<char>(MetaType::kVectorSearchScore) + 1) {
- out->setVectorSearchScore(buf.read<LittleEndian<double>>());
- } else if (marker == static_cast<char>(MetaType::kSearchSequenceToken) + 1) {
- out->setSearchSequenceToken(
- Value::deserializeForSorter(buf, Value::SorterDeserializeSettings()));
+ out->setTimeseriesBucketMaxTime(Date_t::fromMillisSinceEpoch(buf.read<long long>()));
} else {
uasserted(28744, "Unrecognized marker, unable to deserialize buffer");
}
@@ -341,12 +298,6 @@ const char* DocumentMetadataFields::typeNameToDebugString(DocumentMetadataFields
return "timeseries bucket min time";
case DocumentMetadataFields::kTimeseriesBucketMaxTime:
return "timeseries bucket max time";
- case DocumentMetadataFields::kSearchSortValues:
- return "$search sort values";
- case DocumentMetadataFields::kSearchSequenceToken:
- return "$search sequence token";
- case DocumentMetadataFields::kVectorSearchScore:
- return "$vectorSearch distance";
default:
MONGO_UNREACHABLE;
}
diff --git a/src/mongo/db/exec/document_value/document_metadata_fields.h b/src/mongo/db/exec/document_value/document_metadata_fields.h
index 6759675c7ea..12932c29686 100644
--- a/src/mongo/db/exec/document_value/document_metadata_fields.h
+++ b/src/mongo/db/exec/document_value/document_metadata_fields.h
@@ -67,9 +67,6 @@ public:
kSearchScoreDetails,
kTimeseriesBucketMinTime,
kTimeseriesBucketMaxTime,
- kSearchSortValues,
- kVectorSearchScore,
- kSearchSequenceToken,
// New fields must be added before the kNumFields sentinel.
kNumFields
@@ -151,7 +148,11 @@ public:
}
void setTextScore(double score) {
- _setCommon(MetaType::kTextScore);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kTextScore);
_holder->textScore = score;
}
@@ -165,7 +166,11 @@ public:
}
void setRandVal(double val) {
- _setCommon(MetaType::kRandVal);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kRandVal);
_holder->randVal = val;
}
@@ -179,7 +184,11 @@ public:
}
void setSortKey(Value sortKey, bool isSingleElementKey) {
- _setCommon(MetaType::kSortKey);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kSortKey);
_holder->isSingleElementKey = isSingleElementKey;
_holder->sortKey = std::move(sortKey);
}
@@ -198,7 +207,11 @@ public:
}
void setGeoNearDistance(double dist) {
- _setCommon(MetaType::kGeoNearDist);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kGeoNearDist);
_holder->geoNearDistance = dist;
}
@@ -212,7 +225,11 @@ public:
}
void setGeoNearPoint(Value point) {
- _setCommon(MetaType::kGeoNearPoint);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kGeoNearPoint);
_holder->geoNearPoint = std::move(point);
}
@@ -226,7 +243,11 @@ public:
}
void setSearchScore(double score) {
- _setCommon(MetaType::kSearchScore);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kSearchScore);
_holder->searchScore = score;
}
@@ -240,7 +261,11 @@ public:
}
void setSearchHighlights(Value highlights) {
- _setCommon(MetaType::kSearchHighlights);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kSearchHighlights);
_holder->searchHighlights = highlights;
}
@@ -254,7 +279,11 @@ public:
}
void setIndexKey(BSONObj indexKey) {
- _setCommon(MetaType::kIndexKey);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kIndexKey);
_holder->indexKey = indexKey.getOwned();
}
@@ -268,7 +297,11 @@ public:
}
void setRecordId(RecordId rid) {
- _setCommon(MetaType::kRecordId);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+
+ _holder->metaFields.set(MetaType::kRecordId);
_holder->recordId = rid;
}
@@ -282,7 +315,10 @@ public:
}
void setSearchScoreDetails(BSONObj details) {
- _setCommon(MetaType::kSearchScoreDetails);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+ _holder->metaFields.set(MetaType::kSearchScoreDetails);
_holder->searchScoreDetails = details.getOwned();
}
@@ -291,14 +327,15 @@ public:
}
Date_t getTimeseriesBucketMinTime() const {
- tassert(6850100,
- "Document must have timeseries bucket min time metadata field set",
- hasTimeseriesBucketMinTime());
+ invariant(hasTimeseriesBucketMinTime());
return _holder->timeseriesBucketMinTime;
}
void setTimeseriesBucketMinTime(Date_t time) {
- _setCommon(MetaType::kTimeseriesBucketMinTime);
+ if (!_holder) {
+ _holder = std::make_unique<MetadataHolder>();
+ }
+ _holder->metaFields.set(MetaType::kTimeseriesBucketMinTime);
_holder->timeseriesBucketMinTime = time;
}
@@ -307,83 +344,20 @@ public:
}
Date_t getTimeseriesBucketMaxTime() const {
- tassert(6850101,
- "Document must have timeseries bucket max time metadata field set",
- hasTimeseriesBucketMaxTime());
+ invariant(hasTimeseriesBucketMaxTime());
return _holder->timeseriesBucketMaxTime;
}
void setTimeseriesBucketMaxTime(Date_t time) {
- _setCommon(MetaType::kTimeseriesBucketMaxTime);
- _holder->timeseriesBucketMaxTime = time;
- }
-
- bool hasSearchSortValues() const {
- return _holder && _holder->metaFields.test(MetaType::kSearchSortValues);
- }
-
- BSONObj getSearchSortValues() const {
- tassert(7320401, "Document must have $searchSortValues set", hasSearchSortValues());
- return _holder->searchSortValues;
- }
-
- void setSearchSortValues(BSONObj vals) {
- _setCommon(MetaType::kSearchSortValues);
- _holder->searchSortValues = vals.getOwned();
- }
-
- bool hasVectorSearchScore() const {
- return _holder && _holder->metaFields.test(MetaType::kVectorSearchScore);
- }
-
- double getVectorSearchScore() const {
- tassert(7828400, "vectorSearchScore must be present in metadata", hasVectorSearchScore());
- return _holder->vectorSearchScore;
- }
-
- void setVectorSearchScore(double vectorSearchScore) {
- _setCommon(MetaType::kVectorSearchScore);
- _holder->vectorSearchScore = vectorSearchScore;
- }
-
- bool hasSearchSequenceToken() const {
- return _holder && _holder->metaFields.test(MetaType::kSearchSequenceToken);
- }
-
- Value getSearchSequenceToken() const {
- invariant(hasSearchSequenceToken());
- return _holder->searchSequenceToken;
- }
-
- void setSearchSequenceToken(Value details) {
- _setCommon(MetaType::kSearchSequenceToken);
- _holder->searchSequenceToken = details;
- }
-
- void serializeForSorter(BufBuilder& buf) const;
-
- bool isModified() const {
- return _modified;
- }
-
- /**
- * Sets the 'modified' flag to the given value. Necessary for implementing a lazy load
- * optimization for the contained mutable 'DocumentMetadataFields' instance inside the
- * 'Document' class.
- */
- void setModified(bool newValue) {
- _modified = newValue;
- }
-
-private:
- inline void _setCommon(MetaType mt) {
if (!_holder) {
_holder = std::make_unique<MetadataHolder>();
}
- _holder->metaFields.set(mt);
- _modified = true;
+ _holder->metaFields.set(MetaType::kTimeseriesBucketMaxTime);
+ _holder->timeseriesBucketMaxTime = time;
}
+ void serializeForSorter(BufBuilder& buf) const;
+private:
// A simple data struct housing all possible metadata fields.
struct MetadataHolder {
std::bitset<MetaType::kNumFields> metaFields;
@@ -405,18 +379,10 @@ private:
BSONObj searchScoreDetails;
Date_t timeseriesBucketMinTime;
Date_t timeseriesBucketMaxTime;
- BSONObj searchSortValues;
- double vectorSearchScore{0.0};
- Value searchSequenceToken;
};
// Null until the first setter is called, at which point a MetadataHolder struct is allocated.
std::unique_ptr<MetadataHolder> _holder;
-
- // This flag is set to true anytime a 'DocumentMetadataFields' instance is modified. It is used
- // to optimize document conversion to BSON with metadata; i.e. if there are no modifications we
- // can directly return the underlying BSON.
- bool _modified{false};
};
using QueryMetadataBitSet = std::bitset<DocumentMetadataFields::MetaType::kNumFields>;
diff --git a/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp b/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp
index 23ab2277c96..5515009b2bd 100644
--- a/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp
+++ b/src/mongo/db/exec/document_value/document_metadata_fields_test.cpp
@@ -34,7 +34,6 @@
#include "mongo/db/exec/document_value/document_metadata_fields.h"
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/unittest/bson_test_util.h"
-#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
@@ -51,8 +50,6 @@ TEST(DocumentMetadataFieldsTest, AllMetadataRoundtripsThroughSerialization) {
metadata.setIndexKey(BSON("b" << 1));
metadata.setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- metadata.setSearchSortValues(BSON("a" << 1));
- metadata.setVectorSearchScore(7.6);
BufBuilder builder;
metadata.serializeForSorter(builder);
@@ -72,8 +69,6 @@ TEST(DocumentMetadataFieldsTest, AllMetadataRoundtripsThroughSerialization) {
ASSERT_BSONOBJ_EQ(deserialized.getSearchScoreDetails(),
BSON("scoreDetails"
<< "foo"));
- ASSERT_BSONOBJ_EQ(deserialized.getSearchSortValues(), BSON("a" << 1));
- ASSERT_EQ(deserialized.getVectorSearchScore(), 7.6);
}
TEST(DocumentMetadataFieldsTest, HasMethodsReturnFalseForEmptyMetadata) {
@@ -88,8 +83,6 @@ TEST(DocumentMetadataFieldsTest, HasMethodsReturnFalseForEmptyMetadata) {
ASSERT_FALSE(metadata.hasSearchHighlights());
ASSERT_FALSE(metadata.hasIndexKey());
ASSERT_FALSE(metadata.hasSearchScoreDetails());
- ASSERT_FALSE(metadata.hasSearchSortValues());
- ASSERT_FALSE(metadata.hasVectorSearchScore());
}
TEST(DocumentMetadataFieldsTest, HasMethodsReturnTrueForInitializedMetadata) {
@@ -132,14 +125,6 @@ TEST(DocumentMetadataFieldsTest, HasMethodsReturnTrueForInitializedMetadata) {
metadata.setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
ASSERT_TRUE(metadata.hasSearchScoreDetails());
-
- ASSERT_FALSE(metadata.hasSearchSortValues());
- metadata.setSearchSortValues(BSON("a" << 1));
- ASSERT_TRUE(metadata.hasSearchSortValues());
-
- ASSERT_FALSE(metadata.hasVectorSearchScore());
- metadata.setVectorSearchScore(7.6);
- ASSERT_TRUE(metadata.hasVectorSearchScore());
}
TEST(DocumentMetadataFieldsTest, MoveConstructor) {
@@ -154,8 +139,6 @@ TEST(DocumentMetadataFieldsTest, MoveConstructor) {
metadata.setIndexKey(BSON("b" << 1));
metadata.setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- metadata.setSearchSortValues(BSON("a" << 1));
- metadata.setVectorSearchScore(7.6);
DocumentMetadataFields moveConstructed(std::move(metadata));
ASSERT_TRUE(moveConstructed);
@@ -171,8 +154,6 @@ TEST(DocumentMetadataFieldsTest, MoveConstructor) {
ASSERT_BSONOBJ_EQ(moveConstructed.getSearchScoreDetails(),
BSON("scoreDetails"
<< "foo"));
- ASSERT_BSONOBJ_EQ(moveConstructed.getSearchSortValues(), BSON("a" << 1));
- ASSERT_EQ(moveConstructed.getVectorSearchScore(), 7.6);
ASSERT_FALSE(metadata); // NOLINT(bugprone-use-after-move)
}
@@ -189,8 +170,6 @@ TEST(DocumentMetadataFieldsTest, MoveAssignmentOperator) {
metadata.setIndexKey(BSON("b" << 1));
metadata.setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- metadata.setSearchSortValues(BSON("a" << 1));
- metadata.setVectorSearchScore(7.6);
DocumentMetadataFields moveAssigned;
moveAssigned.setTextScore(12.3);
@@ -209,8 +188,6 @@ TEST(DocumentMetadataFieldsTest, MoveAssignmentOperator) {
ASSERT_BSONOBJ_EQ(moveAssigned.getSearchScoreDetails(),
BSON("scoreDetails"
<< "foo"));
- ASSERT_BSONOBJ_EQ(moveAssigned.getSearchSortValues(), BSON("a" << 1));
- ASSERT_EQ(moveAssigned.getVectorSearchScore(), 7.6);
ASSERT_FALSE(metadata); // NOLINT(bugprone-use-after-move)
}
@@ -264,8 +241,6 @@ TEST(DocumentMetadataFieldsTest, MergeWithOnlyCopiesMetadataThatDestinationDoesN
ASSERT_FALSE(destination.hasSearchHighlights());
ASSERT_FALSE(destination.hasIndexKey());
ASSERT_FALSE(destination.hasSearchScoreDetails());
- ASSERT_FALSE(destination.hasSearchSortValues());
- ASSERT_FALSE(destination.hasVectorSearchScore());
}
TEST(DocumentMetadataFieldsTest, CopyFromCopiesAllMetadataThatSourceHas) {
@@ -291,173 +266,6 @@ TEST(DocumentMetadataFieldsTest, CopyFromCopiesAllMetadataThatSourceHas) {
ASSERT_FALSE(destination.hasSearchHighlights());
ASSERT_FALSE(destination.hasIndexKey());
ASSERT_FALSE(destination.hasSearchScoreDetails());
- ASSERT_FALSE(destination.hasSearchSortValues());
- ASSERT_FALSE(destination.hasVectorSearchScore());
-}
-
-TEST(DocumentMetadataFieldsTest, GetTimeseriesBucketMinTimeExists) {
- DocumentMetadataFields source;
- Date_t time;
- source.setTimeseriesBucketMinTime(time);
- ASSERT_EQ(source.getTimeseriesBucketMinTime(), time);
-}
-
-TEST(DocumentMetadataFieldsTest, GetTimeseriesBucketMaxTimeExists) {
- DocumentMetadataFields source;
- Date_t time;
- source.setTimeseriesBucketMaxTime(time);
- ASSERT_EQ(source.getTimeseriesBucketMaxTime(), time);
-}
-
-TEST(DocumentMetadataFieldsTest, MetadataIsMarkedModifiedOnSetMetadataField) {
- // Test setting metadata fields directly.
-
- auto testFieldSetter = [](std::function<void(DocumentMetadataFields&)> invokeSetter) {
- DocumentMetadataFields metadata;
- ASSERT_FALSE(metadata.isModified());
- invokeSetter(metadata);
- ASSERT_TRUE(metadata.isModified());
- };
-
- testFieldSetter([](DocumentMetadataFields& md) { md.setTextScore(10.0); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setRandVal(20.0); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setSortKey(Value(30), true); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setGeoNearDistance(40.0); });
- testFieldSetter(
- [](DocumentMetadataFields& md) { md.setGeoNearPoint(Value{BSON_ARRAY(1 << 2)}); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setSearchScore(50.0); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setSearchHighlights(Value{"foo"_sd}); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setIndexKey(BSON("b" << 1)); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setRecordId(RecordId{6}); });
- testFieldSetter([](DocumentMetadataFields& md) {
- md.setSearchScoreDetails(BSON("scoreDetails"
- << "foo"));
- });
- testFieldSetter([](DocumentMetadataFields& md) { md.setTimeseriesBucketMinTime(Date_t()); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setTimeseriesBucketMaxTime(Date_t()); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setSearchSortValues(BSON("a" << 1)); });
- testFieldSetter([](DocumentMetadataFields& md) { md.setVectorSearchScore(60.0); });
-}
-
-TEST(DocumentMetadataFieldsTest, MetadataIsConstructedUnmodified) {
- // We consider new instances as unmodified for all constructors (default, copy, move) even when
- // the original metadata has the 'modified' flag set.
-
- // Testing the default constructor.
- DocumentMetadataFields metadata1;
- ASSERT_FALSE(metadata1.isModified());
- metadata1.setTextScore(10.0);
- ASSERT_TRUE(metadata1.isModified());
-
- // Testing the copy-constructor.
- DocumentMetadataFields metadata2(metadata1);
- ASSERT_FALSE(metadata2.isModified());
-
- // Testing the move-constructor.
- DocumentMetadataFields metadata3(std::move(metadata1));
- ASSERT_FALSE(metadata3.isModified());
-}
-
-TEST(DocumentMetadataFieldsTest, CopyAssignmentIsModification) {
- // We consider copy-assignment as modification even when the instances are equal and the copied
- // metadata does not have the 'modified' flag set.
-
- DocumentMetadataFields metadata1;
- ASSERT_FALSE(metadata1.isModified());
-
- DocumentMetadataFields metadata2;
- ASSERT_FALSE(metadata2.isModified());
-
- // Testing copy-assignment to empty object (metadata2 == metadata1).
- metadata2 = metadata1;
- ASSERT_TRUE(metadata2.isModified());
-
- // Testing copy-assignment in a more common case (metadata1 is empty, metadata2 is not empty).
- metadata2.setTextScore(10.0);
- metadata2.setModified(false);
- ASSERT_FALSE(metadata2.isModified());
- metadata1 = metadata2;
- ASSERT_TRUE(metadata1.isModified());
- ASSERT_EQ(metadata2.getTextScore(), metadata1.getTextScore());
-}
-
-TEST(DocumentMetadataFieldsTest, MoveAssignmentIsModification) {
- // We consider move-assignment as modification even when the instances are equal and the moved
- // metadata does not have the 'modified' flag set.
-
- DocumentMetadataFields metadata1;
- ASSERT_FALSE(metadata1.isModified());
-
- DocumentMetadataFields metadata2;
- ASSERT_FALSE(metadata2.isModified());
-
- // Testing move-assignment to empty object (metadata2 == metadata1).
- metadata2 = std::move(metadata1);
- ASSERT_TRUE(metadata2.isModified());
-
- // Testing move-assignment in a more common case (metadata3 is empty, metadata2 is not empty).
- metadata2.setTextScore(10.0);
- metadata2.setModified(false);
- ASSERT_FALSE(metadata2.isModified());
-
- DocumentMetadataFields metadata3;
- ASSERT_FALSE(metadata3.isModified());
-
- metadata3 = std::move(metadata2);
- ASSERT_TRUE(metadata3.isModified());
- ASSERT_EQ(10.0, metadata3.getTextScore());
-}
-
-TEST(DocumentMetadataFieldsTest, MetadataIsMarkedModifiedOnCopyFrom) {
- DocumentMetadataFields metadata1;
- metadata1.setRandVal(20.0);
- ASSERT_TRUE(metadata1.isModified());
-
- // Testing 'setModified(false)'.
- metadata1.setModified(false);
- ASSERT_FALSE(metadata1.isModified());
-
- // Calling 'copyFrom(metadata1)' modifies metadata2 even when metadata2 == metadata1 and
- // metadata1 is not marked as modified.
- DocumentMetadataFields metadata2(metadata1);
- ASSERT_FALSE(metadata2.isModified());
- metadata2.copyFrom(metadata1);
- ASSERT_TRUE(metadata2.isModified());
-}
-
-TEST(DocumentMetadataFieldsTest, MetadataIsMarkedModifiedOnMergeWith) {
- DocumentMetadataFields metadata1;
- metadata1.setRandVal(20.0);
- ASSERT_TRUE(metadata1.isModified());
-
- metadata1.setModified(false);
- ASSERT_FALSE(metadata1.isModified());
-
- // Calling 'mergeWith(metadata1)' modifies metadata2 only when metadata1 has fields not set in
- // metadata1.
- DocumentMetadataFields metadata2(metadata1);
- ASSERT_FALSE(metadata2.isModified());
- metadata2.mergeWith(metadata1);
- ASSERT_FALSE(metadata2.isModified());
-
- DocumentMetadataFields metadata3;
- ASSERT_FALSE(metadata3.isModified());
- metadata3.mergeWith(metadata2);
- ASSERT_TRUE(metadata3.isModified());
-}
-
-DEATH_TEST_REGEX(DocumentMetadataFieldsTest,
- GetTimeseriesBucketMinTimeDoesntExist,
- "Tripwire assertion.*6850100") {
- DocumentMetadataFields source;
- source.getTimeseriesBucketMinTime();
-}
-
-DEATH_TEST_REGEX(DocumentMetadataFieldsTest,
- GetTimeseriesBucketMaxTimeDoesntExist,
- "Tripwire assertion.*6850101") {
- DocumentMetadataFields source;
- source.getTimeseriesBucketMaxTime();
}
} // namespace mongo
diff --git a/src/mongo/db/exec/document_value/document_value_test.cpp b/src/mongo/db/exec/document_value/document_value_test.cpp
index ca1123a7555..421da707001 100644
--- a/src/mongo/db/exec/document_value/document_value_test.cpp
+++ b/src/mongo/db/exec/document_value/document_value_test.cpp
@@ -355,89 +355,6 @@ TEST(DocumentGetFieldNonCaching, TraverseArray) {
checkArrayTagIsReturned();
}
-TEST(DocumentSize, ApproximateSizeIsSnapshotted) {
- const auto rawBson = BSON("field"
- << "value");
- const Document document{rawBson};
- const auto noCacheSize = document.getApproximateSize();
-
- // Force the cache construction, making the total size of the 'Document' bigger.
- // 'getApproximateSize()' must still return the same value.
- document["field"];
- const auto fullCacheSizeSnapshot = document.getApproximateSize();
- const auto fullCacheSizeCurrent = document.getCurrentApproximateSize();
- ASSERT_EQ(noCacheSize, fullCacheSizeSnapshot);
- ASSERT_LT(noCacheSize, fullCacheSizeCurrent);
-}
-
-TEST(DocumentSize, ApproximateSizeDuringBuildIsUpdated) {
- MutableDocument builder;
- builder.addField("a1", Value(1));
- builder.addField("a2", mongo::Value(2));
- builder.addField("a3", mongo::Value(3));
- auto middleBuildSize = builder.getApproximateSize();
-
- builder.addField("a4", Value(4));
- builder.addField("a5", mongo::Value(5));
- builder.addField("a6", mongo::Value(6));
- auto peekSize = builder.peek().getApproximateSize();
-
- builder.addField("a7", Value(7));
- builder.addField("a8", mongo::Value(8));
- builder.addField("a9", mongo::Value(9));
- auto beforeFreezeSize = builder.getApproximateSize();
-
- Document result = builder.freeze();
- auto frozenSize = result.getApproximateSize();
-
- ASSERT_LT(middleBuildSize, peekSize);
- ASSERT_LT(peekSize, beforeFreezeSize);
- ASSERT_EQ(beforeFreezeSize, frozenSize);
-}
-
-TEST(ShredDocument, OutputHasNoBackingBSON) {
- BSONObj bson =
- BSON("a" << 1 << "subObj" << BSON("a" << 1) << "subArray" << BSON_ARRAY(BSON("a" << 1)));
- auto original = fromBson(bson);
- auto originalSize = original.getApproximateSize();
-
- auto shredded = original.shred();
- auto originalSizeAfterShredding = original.getApproximateSize();
- // Fields in the original doc shouldn't be cached since it was raw bson
- ASSERT_EQ(originalSize, originalSizeAfterShredding);
-
- // BSON is more compact than ValueElement
- auto shreddedSize = shredded.getApproximateSize();
- ASSERT_LT(originalSize, shreddedSize);
-
- // Accessing a field shouldn't change the size since all fields are already cached.
- shredded["a"];
- ASSERT_EQ(shredded.getCurrentApproximateSize(), shreddedSize);
-}
-
-TEST(ShredDocument, HandlesModifiedDocuments) {
- BSONObj bson = BSON("a" << 1 << "subObj" << BSON("a" << 1));
- Document original = fromBson(bson);
- MutableDocument md(original);
- md["b"] = Value(2);
- md["subObj"]["b"] = Value(2);
- Document shredded = md.freeze().shred();
-
- ASSERT(!shredded["b"].missing());
- ASSERT(!shredded["subObj"]["b"].missing());
-}
-
-TEST(ShredDocument, HandlesMetadata) {
- BSONObj bson = BSON("a" << 1 << "subObj" << BSON("a" << 1));
- Document original = fromBson(bson);
- MutableDocument md(original);
- DocumentMetadataFields meta;
- meta.setSearchScore(6);
- md.setMetadata(std::move(meta));
- Document shredded = md.freeze().shred();
- ASSERT_EQ(6, shredded.metadata().getSearchScore());
-}
-
/** Add Document fields. */
class AddField {
public:
@@ -788,22 +705,6 @@ public:
BSONObjBuilder objBuilder;
BSONArrayBuilder arrBuilder;
};
-
-TEST(DocumentTest, ToBsonSizeTraits) {
- constexpr size_t longStringLength = 9 * 1024 * 1024;
- static_assert(longStringLength <= BSONObjMaxInternalSize &&
- 2 * longStringLength > BSONObjMaxInternalSize &&
- 2 * longStringLength <= BufferMaxSize);
- std::string longString(longStringLength, 'A');
- MutableDocument md;
- md.addField("a", Value(longString));
- ASSERT_DOES_NOT_THROW(md.peek().toBson());
- md.addField("b", Value(longString));
- ASSERT_THROWS_CODE(md.peek().toBson(), DBException, ErrorCodes::BSONObjectTooLarge);
- ASSERT_THROWS_CODE(
- md.peek().toBson<BSONObj::DefaultSizeTrait>(), DBException, ErrorCodes::BSONObjectTooLarge);
- ASSERT_DOES_NOT_THROW(md.peek().toBson<BSONObj::LargeSizeTrait>());
-}
} // namespace Document
namespace MetaFields {
@@ -930,15 +831,6 @@ TEST(MetaFields, FromBsonWithMetadataAcceptsIndexKeyMetadata) {
ASSERT_BSONOBJ_EQ(bsonWithoutMetadata, BSON("a" << 1));
}
-TEST(MetaFields, FromBsonWithMetadataHandlesEmptyFieldName) {
- auto bson = BSON("" << 1 << "$indexKey" << BSON("b" << 1));
- auto doc = Document::fromBsonWithMetaData(bson);
- ASSERT_TRUE(doc.metadata().hasIndexKey());
- ASSERT_BSONOBJ_EQ(doc.metadata().getIndexKey(), BSON("b" << 1));
- auto bsonWithoutMetadata = doc.toBson();
- ASSERT_BSONOBJ_EQ(bsonWithoutMetadata, BSON("" << 1));
-}
-
TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) {
Document source = Document::fromBsonWithMetaData(
BSON("a" << 1 << "$textScore" << 9.9 << "b" << 1 << "$randVal" << 42.0 << "c" << 1
@@ -948,8 +840,7 @@ TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) {
<< "foo"
<< "h" << 1 << "$indexKey" << BSON("y" << 1) << "$searchScoreDetails"
<< BSON("scoreDetails"
- << "foo")
- << "$searchSortValues" << BSON("a" << 1) << "$vectorSearchScore" << 6.7));
+ << "foo")));
MutableDocument destination{};
destination.copyMetaDataFrom(source);
@@ -966,8 +857,6 @@ TEST(MetaFields, CopyMetadataFromCopiesAllMetadata) {
ASSERT_BSONOBJ_EQ(result.metadata().getSearchScoreDetails(),
BSON("scoreDetails"
<< "foo"));
- ASSERT_BSONOBJ_EQ(result.metadata().getSearchSortValues(), BSON("a" << 1));
- ASSERT_EQ(result.metadata().getVectorSearchScore(), 6.7);
}
class SerializationTest : public unittest::Test {
@@ -988,8 +877,6 @@ protected:
ASSERT_EQ(output.metadata().hasSearchScore(), input.metadata().hasSearchScore());
ASSERT_EQ(output.metadata().hasSearchHighlights(), input.metadata().hasSearchHighlights());
ASSERT_EQ(output.metadata().hasIndexKey(), input.metadata().hasIndexKey());
- ASSERT_EQ(output.metadata().hasVectorSearchScore(),
- input.metadata().hasVectorSearchScore());
if (input.metadata().hasTextScore()) {
ASSERT_EQ(output.metadata().getTextScore(), input.metadata().getTextScore());
}
@@ -1010,10 +897,6 @@ protected:
ASSERT_BSONOBJ_EQ(output.metadata().getSearchScoreDetails(),
input.metadata().getSearchScoreDetails());
}
- if (input.metadata().hasVectorSearchScore()) {
- ASSERT_EQ(output.metadata().getVectorSearchScore(),
- input.metadata().getVectorSearchScore());
- }
ASSERT(output.toBson().binaryEqual(input.toBson()));
}
@@ -1028,7 +911,6 @@ TEST_F(SerializationTest, MetaSerializationNoVals) {
<< "def"_sd));
docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- docBuilder.metadata().setVectorSearchScore(40.0);
assertRoundTrips(docBuilder.freeze());
}
@@ -1043,7 +925,6 @@ TEST_F(SerializationTest, MetaSerializationWithVals) {
docBuilder.metadata().setIndexKey(BSON("key" << 42));
docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- docBuilder.metadata().setVectorSearchScore(40.0);
assertRoundTrips(docBuilder.freeze());
}
@@ -1066,8 +947,6 @@ TEST(MetaFields, ToAndFromBson) {
<< "def"_sd));
docBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- docBuilder.metadata().setSearchSortValues(BSON("a" << 42));
- docBuilder.metadata().setVectorSearchScore(40.0);
Document doc = docBuilder.freeze();
BSONObj obj = doc.toBsonWithMetaData();
ASSERT_EQ(10.0, obj[Document::metaFieldTextScore].Double());
@@ -1079,8 +958,6 @@ TEST(MetaFields, ToAndFromBson) {
ASSERT_BSONOBJ_EQ(obj[Document::metaFieldSearchScoreDetails].Obj(),
BSON("scoreDetails"
<< "foo"));
- ASSERT_BSONOBJ_EQ(BSON("a" << 42), obj[Document::metaFieldSearchSortValues].Obj());
- ASSERT_EQ(40.0, obj[Document::metaFieldVectorSearchScore].Double());
Document fromBson = Document::fromBsonWithMetaData(obj);
ASSERT_TRUE(fromBson.metadata().hasTextScore());
ASSERT_TRUE(fromBson.metadata().hasRandVal());
@@ -1089,110 +966,6 @@ TEST(MetaFields, ToAndFromBson) {
ASSERT_BSONOBJ_EQ(BSON("scoreDetails"
<< "foo"),
fromBson.metadata().getSearchScoreDetails());
- ASSERT_BSONOBJ_EQ(BSON("a" << 42), fromBson.metadata().getSearchSortValues());
- ASSERT_EQ(40.0, fromBson.metadata().getVectorSearchScore());
-}
-
-TEST(MetaFields, ToAndFromBsonTrivialConvertibility) {
- Value sortKey{Document{{"token"_sd, "SOMENCODEDATA"_sd}}};
- // Create a document with a backing BSONObj and separate metadata.
- auto origObjNoMetadata = BSON("a" << 42);
- ASSERT_FALSE(origObjNoMetadata.hasField(Document::metaFieldSortKey));
-
- MutableDocument docBuilder;
- docBuilder.reset(origObjNoMetadata, false);
- docBuilder.metadata().setSortKey(sortKey, true);
- Document docWithSeparateBsonAndMetadata = docBuilder.freeze();
-
- BSONObj origObjWithMetadata = docWithSeparateBsonAndMetadata.toBsonWithMetaData();
- ASSERT_TRUE(origObjWithMetadata.hasField(Document::metaFieldSortKey));
- Document restoredDocWithMetadata = Document::fromBsonWithMetaData(origObjWithMetadata);
- ASSERT_DOCUMENT_EQ(docWithSeparateBsonAndMetadata, restoredDocWithMetadata);
-
- // Test the 'isTriviallyConvertible()' function.
- // The original document is trivially convertible without metadata because the metadata was
- // added to the document separately from the backing BSON object.
- ASSERT_TRUE(docWithSeparateBsonAndMetadata.isTriviallyConvertible());
- // The original document is NOT trivially convertible with metadata because the metadata was
- // added to the document and does not exist in the BSONObj.
- ASSERT_FALSE(docWithSeparateBsonAndMetadata.isTriviallyConvertibleWithMetadata());
- // The restored document is trivially convertible with metadata because the underlying BSONObj
- // contains the metadata serialized from the original document.
- ASSERT_TRUE(restoredDocWithMetadata.isTriviallyConvertibleWithMetadata());
- // The restored document is NOT trivially convertible without metadata because the metadata
- // fields need to be stripped from the underlying BSONObj.
- ASSERT_FALSE(restoredDocWithMetadata.isTriviallyConvertible());
-
- // Test that the conversion with metadata 'origObjWithMetadata' -> 'restoredDocWithMetadata' ->
- // 'restoredObjWithMetadata' is trivial because the backing BSON already contains metadata and
- // neither the metadata nor the non-metadata fields have been modified.
- BSONObj restoredObjWithMetadata = restoredDocWithMetadata.toBsonWithMetaData();
- ASSERT_TRUE(restoredObjWithMetadata.hasField(Document::metaFieldSortKey));
- // Test that 'restoredObjWithMetadata' is referring to the exact same memory location as
- // 'origObjWithMetadata', i.e. both objdata() and objsize() match.
- ASSERT_EQ(origObjWithMetadata.objdata(), restoredObjWithMetadata.objdata());
- ASSERT_EQ(origObjWithMetadata.objsize(), restoredObjWithMetadata.objsize());
-
- // Test that the conversion without metadata 'origObjWithMetadata' -> 'restoredDocWithMetadata'
- // -> 'strippedRestoredObj' is NOT trivial because the backing BSON has metadata that must be
- // omitted during serialization.
- BSONObj strippedRestoredObj = restoredDocWithMetadata.toBson();
- ASSERT_FALSE(strippedRestoredObj.hasField(Document::metaFieldSortKey));
- // 'restoredDocWithMetadata' is trivially convertible with metadata and converting it to BSON
- // without metadata will return a new BSON object.
- ASSERT_TRUE(origObjNoMetadata.binaryEqual(strippedRestoredObj));
- ASSERT_NE(origObjNoMetadata.objdata(), strippedRestoredObj.objdata());
-
- // Test that the conversion without metadata 'origObjNoMetadata' ->
- // 'docWithSeparateBsonAndMetadata' -> 'restoredObjNoMetadata' is trivial because
- // 'origObjNoMetadata' does not contain any metadata.
- BSONObj restoredObjNoMetadata = docWithSeparateBsonAndMetadata.toBson();
- ASSERT_FALSE(restoredObjNoMetadata.hasField(Document::metaFieldSortKey));
- // Test that 'restoredObjNoMetadata' is referring to the exact same memory location as
- // 'origObjNoMetadata', i.e. both objdata() and objsize() match.
- ASSERT_EQ(origObjNoMetadata.objdata(), restoredObjNoMetadata.objdata());
- ASSERT_EQ(origObjNoMetadata.objsize(), restoredObjNoMetadata.objsize());
-}
-
-TEST(MetaFields, TrivialConvertibilityBsonWithoutMetadata) {
- // Test that an unmodified document without metadata is trivially convertible to BSON with and
- // without metadata.
- auto bsonWithoutMetadata = BSON("a" << 42);
- Document doc(bsonWithoutMetadata);
- ASSERT_TRUE(doc.isTriviallyConvertible());
- ASSERT_TRUE(doc.isTriviallyConvertibleWithMetadata());
-}
-
-TEST(MetaFields, TrivialConvertibilityNoBson) {
- // A Document created with no backing BSON is not trivially convertible.
- auto docNoBson = Document{{"a", 42}};
- ASSERT_FALSE(docNoBson.isTriviallyConvertible());
- ASSERT_FALSE(docNoBson.isTriviallyConvertibleWithMetadata());
-
- // An empty Document is trivially convertible, since the default BSONObj is also empty.
- auto emptyDoc = Document{};
- ASSERT_TRUE(emptyDoc.isTriviallyConvertible());
- ASSERT_TRUE(emptyDoc.isTriviallyConvertibleWithMetadata());
-}
-
-TEST(MetaFields, TrivialConvertibilityModified) {
- // Modifying a document with a backing BSON renders it not trivially convertible.
- MutableDocument mutDocModified(Document(BSON("a" << 42)));
- mutDocModified.addField("b", Value(43));
- auto modifiedDoc = mutDocModified.freeze();
- ASSERT_FALSE(modifiedDoc.isTriviallyConvertible());
- ASSERT_FALSE(modifiedDoc.isTriviallyConvertibleWithMetadata());
-}
-
-TEST(MetaFields, TrivialConvertibilityMetadataModified) {
- // Modifying the metadata of a document with a backing BSON renders it not trivially convertible
- // with metadata.
- MutableDocument mutDocModifiedMd(
- Document::fromBsonWithMetaData(BSON(Document::metaFieldTextScore << 10.0)));
- mutDocModifiedMd.metadata().setRandVal(20.0);
- auto modifiedMdDoc = mutDocModifiedMd.freeze();
- ASSERT_FALSE(modifiedMdDoc.isTriviallyConvertible());
- ASSERT_FALSE(modifiedMdDoc.isTriviallyConvertibleWithMetadata());
}
TEST(MetaFields, MetaFieldsIncludedInDocumentApproximateSize) {
@@ -1210,10 +983,8 @@ TEST(MetaFields, MetaFieldsIncludedInDocumentApproximateSize) {
const size_t bigMetadataDocSize = doc2.getApproximateSize();
ASSERT_GT(bigMetadataDocSize, smallMetadataDocSize);
- // Do a sanity check on the amount of space taken by metadata in document 2. Note that the size
- // of certain data types may vary on different build variants, so we cannot assert on the exact
- // size.
- ASSERT_LT(doc2.getMetadataApproximateSize(), 400U);
+ // Do a sanity check on the amount of space taken by metadata in document 2.
+ ASSERT_LT(doc2.getMetadataApproximateSize(), 300U);
Document emptyDoc;
ASSERT_LT(emptyDoc.getMetadataApproximateSize(), 100U);
diff --git a/src/mongo/db/exec/document_value/document_value_test_util.h b/src/mongo/db/exec/document_value/document_value_test_util.h
index 7b88c9688fa..b6959a7d17f 100644
--- a/src/mongo/db/exec/document_value/document_value_test_util.h
+++ b/src/mongo/db/exec/document_value/document_value_test_util.h
@@ -59,13 +59,6 @@
#define _ASSERT_DOCVAL_COMPARISON(NAME, a, b) \
::mongo::unittest::assertComparison_##NAME(__FILE__, __LINE__, #a, #b, a, b)
-// TODO SERVER-87736 make these not say "AUTO".
-// These are backport-special macros, adapted from the "AUTO" version on more recent branches. The
-// automatic functionality doesn't exist on this branch. But the assertions should still pass.
-#define ASSERT_VALUE_EQ_AUTO(expected, val) ASSERT_EQ(expected, val.toString())
-#define ASSERT_DOCUMENT_EQ_AUTO(expected, actual) \
- ASSERT_BSONOBJ_EQ(fromjson(expected), actual.toBson())
-
namespace mongo {
namespace unittest {
diff --git a/src/mongo/db/exec/document_value/value.cpp b/src/mongo/db/exec/document_value/value.cpp
index 616efb2128a..248514180f0 100644
--- a/src/mongo/db/exec/document_value/value.cpp
+++ b/src/mongo/db/exec/document_value/value.cpp
@@ -45,7 +45,6 @@
#include "mongo/db/query/datetime/date_time_support.h"
#include "mongo/platform/decimal128.h"
#include "mongo/util/hex.h"
-#include "mongo/util/murmur3.h"
#include "mongo/util/represent_as.h"
#include "mongo/util/str.h"
@@ -936,7 +935,7 @@ void Value::hash_combine(size_t& seed,
case Code:
case Symbol: {
StringData sd = getRawData();
- seed = murmur3<sizeof(size_t)>(sd, seed);
+ MurmurHash3_x86_32(sd.rawData(), sd.size(), seed, &seed);
break;
}
@@ -945,7 +944,7 @@ void Value::hash_combine(size_t& seed,
if (stringComparator) {
stringComparator->hash_combine(seed, sd);
} else {
- seed = murmur3<sizeof(size_t)>(sd, seed);
+ MurmurHash3_x86_32(sd.rawData(), sd.size(), seed, &seed);
}
break;
}
@@ -969,14 +968,14 @@ void Value::hash_combine(size_t& seed,
case BinData: {
StringData sd = getRawData();
- seed = murmur3<sizeof(size_t)>(sd, seed);
+ MurmurHash3_x86_32(sd.rawData(), sd.size(), seed, &seed);
boost::hash_combine(seed, _storage.binDataType());
break;
}
case RegEx: {
StringData sd = getRawData();
- seed = murmur3<sizeof(size_t)>(sd, seed);
+ MurmurHash3_x86_32(sd.rawData(), sd.size(), seed, &seed);
break;
}
@@ -1254,17 +1253,14 @@ ostream& operator<<(ostream& out, const Value& val) {
verify(false);
}
-Value Value::shred() const {
+void Value::fillCache() const {
if (isObject()) {
- return Value(getDocument().shred());
+ getDocument().fillCache();
} else if (isArray()) {
- std::vector<Value> values;
for (auto&& val : getArray()) {
- values.push_back(val.shred());
+ val.fillCache();
}
- return Value(values);
}
- return Value(*this);
}
void Value::serializeForSorter(BufBuilder& buf) const {
diff --git a/src/mongo/db/exec/document_value/value.h b/src/mongo/db/exec/document_value/value.h
index 62a31f25727..bf54221326c 100644
--- a/src/mongo/db/exec/document_value/value.h
+++ b/src/mongo/db/exec/document_value/value.h
@@ -344,9 +344,9 @@ public:
friend std::ostream& operator<<(std::ostream& out, const Value& v);
/**
- * Returns a cache-only copy of the value with no backing bson.
+ * Populates the internal cache by recursively walking the underlying BSON.
*/
- Value shred() const;
+ void fillCache() const;
void swap(Value& rhs) {
_storage.swap(rhs._storage);
@@ -430,16 +430,13 @@ public:
ImplicitValue(T&& arg) : Value(std::forward<T>(arg)) {}
ImplicitValue(std::initializer_list<ImplicitValue> values) : Value(convertToValues(values)) {}
- ImplicitValue(std::vector<ImplicitValue> values) : Value(convertToValues(values)) {}
- template <typename T>
- ImplicitValue(std::vector<T> values) : Value(convertToValues(values)) {}
+ ImplicitValue(std::vector<int> values) : Value(convertToValues(values)) {}
- template <typename T>
- static std::vector<Value> convertToValues(const std::vector<T>& vec) {
+ static std::vector<Value> convertToValues(const std::vector<int>& vec) {
std::vector<Value> values;
values.reserve(vec.size());
- for_each(vec.begin(), vec.end(), ([&](const T& val) { values.emplace_back(val); }));
+ for_each(vec.begin(), vec.end(), ([&](const int& val) { values.emplace_back(val); }));
return values;
}
diff --git a/src/mongo/db/exec/document_value/value_comparator_test.cpp b/src/mongo/db/exec/document_value/value_comparator_test.cpp
index ac854fdadbe..963a29bc11a 100644
--- a/src/mongo/db/exec/document_value/value_comparator_test.cpp
+++ b/src/mongo/db/exec/document_value/value_comparator_test.cpp
@@ -312,33 +312,5 @@ TEST(ValueComparatorTest, HashingCodeShouldNotRespectCollation) {
ASSERT_NE(comparator.hash(val1), comparator.hash(val2));
}
-// This test was originally designed to reproduce SERVER-78126.
-TEST(ValueComparatorTest, ArraysDifferingByOneStringShouldHaveDifferentHashes) {
- const ValueComparator comparator{};
- const Value val1{std::vector<Value>{Value{std::string{"a"}}, Value{std::string{"x"}}}};
- const Value val2{std::vector<Value>{Value{std::string{"b"}}, Value{std::string{"x"}}}};
- ASSERT_NE(comparator.compare(val1, val2), 0);
- ASSERT_NE(comparator.hash(val1), comparator.hash(val2));
-}
-
-TEST(ValueComparatorTest, ArraysDifferingByOneStringShouldHaveDifferentHashesWithCollation) {
- CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
- const ValueComparator comparator{&collator};
- const Value val1{std::vector<Value>{Value{std::string{"abc"}}, Value{std::string{"xyz"}}}};
- const Value val2{std::vector<Value>{Value{std::string{"bcd"}}, Value{std::string{"xyz"}}}};
- ASSERT_NE(comparator.compare(val1, val2), 0);
- ASSERT_NE(comparator.hash(val1), comparator.hash(val2));
-}
-
-TEST(ValueComparatorTest, ObjectsDifferingByOneStringShouldHaveDifferentHashes) {
- const ValueComparator comparator{};
- const Value val1(
- Document({{"foo"_sd, Value{std::string{"abc"}}}, {"bar"_sd, Value{std::string{"xyz"}}}}));
- const Value val2(
- Document({{"foo"_sd, Value{std::string{"def"}}}, {"bar"_sd, Value{std::string{"xyz"}}}}));
- ASSERT_NE(comparator.compare(val1, val2), 0);
- ASSERT_NE(comparator.hash(val1), comparator.hash(val2));
-}
-
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/exec/exclusion_projection_executor.cpp b/src/mongo/db/exec/exclusion_projection_executor.cpp
index 2061bf4fe93..9823ed1b125 100644
--- a/src/mongo/db/exec/exclusion_projection_executor.cpp
+++ b/src/mongo/db/exec/exclusion_projection_executor.cpp
@@ -38,15 +38,14 @@ std::pair<BSONObj, bool> ExclusionNode::extractProjectOnFieldAndRename(const Str
BSONObjBuilder extractedExclusion;
// Check for a projection directly on 'oldName'. For example, {oldName: 0}.
- if (auto it = _projectedFieldsSet.find(oldName); it != _projectedFieldsSet.end()) {
+ if (auto it = _projectedFields.find(oldName); it != _projectedFields.end()) {
extractedExclusion.append(newName, false);
- _projectedFieldsSet.erase(it);
- _projectedFields.remove(std::string(oldName));
+ _projectedFields.erase(it);
}
// Check for a projection on subfields of 'oldName'. For example, {oldName: {a: 0, b: 0}}.
if (auto it = _children.find(oldName); it != _children.end()) {
- extractedExclusion.append(newName, it->second->serialize(boost::none, {}).toBson());
+ extractedExclusion.append(newName, it->second->serialize(boost::none).toBson());
_children.erase(it);
}
diff --git a/src/mongo/db/exec/exclusion_projection_executor.h b/src/mongo/db/exec/exclusion_projection_executor.h
index 37623206723..a9c1fade72d 100644
--- a/src/mongo/db/exec/exclusion_projection_executor.h
+++ b/src/mongo/db/exec/exclusion_projection_executor.h
@@ -86,9 +86,6 @@ protected:
Value transformSkippedValueForOutput(const Value& value) const final {
return value;
}
- bool isIncluded() const final {
- return false;
- }
};
/**
@@ -99,12 +96,10 @@ protected:
*/
class ExclusionProjectionExecutor : public ProjectionExecutor {
public:
- ExclusionProjectionExecutor(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies,
- bool allowFastPath = false,
- boost::optional<projection_ast::ProjectionPathASTNode> proj = boost::none)
- : ProjectionExecutor(expCtx, policies, proj), _root(new ExclusionNode(_policies)) {}
+ ExclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies,
+ bool allowFastPath = false)
+ : ProjectionExecutor(expCtx, policies), _root(new ExclusionNode(_policies)) {}
TransformerType getType() const final {
return TransformerType::kExclusionProjection;
@@ -118,17 +113,16 @@ public:
return _root.get();
}
- Document serializeTransformation(boost::optional<ExplainOptions::Verbosity> explain,
- const SerializationOptions& options = {}) const final {
+ Document serializeTransformation(
+ boost::optional<ExplainOptions::Verbosity> explain) const final {
MutableDocument output;
// The ExclusionNode tree in '_root' will always have a top-level _id node if _id is to be
// excluded. If the _id node is not present, then explicitly set {_id: true} to avoid
// ambiguity in the expected behavior of the serialized projection.
- _root->serialize(explain, &output, options);
- auto idFieldName = options.serializeFieldPath("_id");
- if (output.peek()[idFieldName].missing()) {
- output.addField(idFieldName, Value{true});
+ _root->serialize(explain, &output);
+ if (output.peek()["_id"].missing()) {
+ output.addField("_id", Value{true});
}
return output.freeze();
}
@@ -155,7 +149,7 @@ public:
return {DocumentSource::GetModPathsReturn::Type::kAllPaths, {}, {}};
}
- OrderedPathSet modifiedPaths;
+ std::set<std::string> modifiedPaths;
_root->reportProjectedPaths(&modifiedPaths);
return {DocumentSource::GetModPathsReturn::Type::kFiniteSet, std::move(modifiedPaths), {}};
}
diff --git a/src/mongo/db/exec/exclusion_projection_executor_test.cpp b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
index c3866ef865f..983c5e1995a 100644
--- a/src/mongo/db/exec/exclusion_projection_executor_test.cpp
+++ b/src/mongo/db/exec/exclusion_projection_executor_test.cpp
@@ -363,8 +363,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldEvaluateMetaExpressions) {
"i: {$meta: 'recordId'}, "
"j: {$meta: 'indexKey'}, "
"k: {$meta: 'sortKey'}, "
- "l: {$meta: 'searchScoreDetails'}, "
- "m: {$meta: 'vectorSearchScore'}}"));
+ "l: {$meta: 'searchScoreDetails'}}"));
MutableDocument inputDocBuilder(Document{{"a", 1}, {"b", 2}});
inputDocBuilder.metadata().setTextScore(0.0);
@@ -378,7 +377,6 @@ TEST(ExclusionProjectionExecutionTest, ShouldEvaluateMetaExpressions) {
inputDocBuilder.metadata().setSortKey(Value{Document{{"bar", 8}}}, true);
inputDocBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- inputDocBuilder.metadata().setVectorSearchScore(9.0);
Document inputDoc = inputDocBuilder.freeze();
auto result = exclusion->applyTransformation(inputDoc);
@@ -386,7 +384,7 @@ TEST(ExclusionProjectionExecutionTest, ShouldEvaluateMetaExpressions) {
ASSERT_DOCUMENT_EQ(result,
Document{fromjson("{b: 2, c: 0.0, d: 1.0, e: 2.0, f: 'foo', g: 3.0, "
"h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}],"
- "l: {scoreDetails: 'foo'}, m: 9.0}")});
+ "l: {scoreDetails: 'foo'}}")});
}
TEST(ExclusionProjectionExecutionTest, ShouldAddMetaExpressionsToDependencies) {
@@ -400,20 +398,18 @@ TEST(ExclusionProjectionExecutionTest, ShouldAddMetaExpressionsToDependencies) {
"i: {$meta: 'recordId'}, "
"j: {$meta: 'indexKey'}, "
"k: {$meta: 'sortKey'}, "
- "l: {$meta: 'searchScoreDetails'}}, "
- "m: {$meta: 'vectorSearchScore'}"));
+ "l: {$meta: 'searchScoreDetails'}}"));
DepsTracker deps;
exclusion->addDependencies(&deps);
ASSERT_EQ(deps.fields.size(), 0UL);
- // We do not add the dependencies for searchScore, searchHighlights, searchScoreDetails, or
- // distance because those values are not stored in the collection (or in mongod at all).
+ // We do not add the dependencies for searchScore, searchHighlights, or searchScoreDetails
+ // because those values are not stored in the collection (or in mongod at all).
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScore]);
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchHighlights]);
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScoreDetails]);
- ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kVectorSearchScore]);
ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kTextScore]);
ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kRandVal]);
diff --git a/src/mongo/db/exec/fetch.cpp b/src/mongo/db/exec/fetch.cpp
index 2059faf7d79..9d51dad5ca4 100644
--- a/src/mongo/db/exec/fetch.cpp
+++ b/src/mongo/db/exec/fetch.cpp
@@ -181,8 +181,10 @@ unique_ptr<PlanStageStats> FetchStage::getStats() {
_commonStats.isEOF = isEOF();
// Add a BSON representation of the filter to the stats tree, if there is one.
- if (_filter) {
- _commonStats.filter = _filter->serialize();
+ if (nullptr != _filter) {
+ BSONObjBuilder bob;
+ _filter->serialize(&bob);
+ _commonStats.filter = bob.obj();
}
unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_FETCH);
diff --git a/src/mongo/db/exec/inclusion_projection_executor.cpp b/src/mongo/db/exec/inclusion_projection_executor.cpp
index d06cedf61b6..d7384be8109 100644
--- a/src/mongo/db/exec/inclusion_projection_executor.cpp
+++ b/src/mongo/db/exec/inclusion_projection_executor.cpp
@@ -67,7 +67,7 @@ void FastPathEligibleInclusionNode::_applyProjections(BSONObj bson, BSONObjBuild
const auto bsonElement{it.next()};
const auto fieldName{bsonElement.fieldNameStringData()};
- if (_projectedFieldsSet.find(fieldName) != _projectedFieldsSet.end()) {
+ if (_projectedFields.find(fieldName) != _projectedFields.end()) {
bob->append(bsonElement);
--nFieldsNeeded;
} else if (auto childIt = _children.find(fieldName); childIt != _children.end()) {
@@ -99,55 +99,6 @@ boost::intrusive_ptr<Expression> substituteInExpr(boost::intrusive_ptr<Expressio
}
return ex;
};
-
-/**
- * Returns a vector of top-level dependencies where each index i in the vector corresponds to the
- * dependencies from the ith expression according to 'orderToProcess'. Will return boost::none if
- * any expression needs the whole document.
- */
-boost::optional<std::vector<OrderedPathSet>> getTopLevelDeps(
- const std::vector<std::string>& orderToProcess,
- const StringMap<boost::intrusive_ptr<Expression>>& expressions,
- const StringMap<std::unique_ptr<ProjectionNode>>& children) {
- std::vector<OrderedPathSet> topLevelDeps;
- for (const auto& field : orderToProcess) {
- DepsTracker deps;
- if (auto exprIt = expressions.find(field); exprIt != expressions.end()) {
- exprIt->second->addDependencies(&deps);
- } else {
- // Each expression in orderToProcess should either be in expressions or children.
- auto childIt = children.find(field);
- tassert(6657000, "Unable to calculate dependencies", childIt != children.end());
- childIt->second->reportDependencies(&deps);
- }
-
- if (deps.needWholeDocument) {
- return boost::none;
- }
-
- OrderedPathSet ops{deps.fields.begin(), deps.fields.end()};
- topLevelDeps.push_back(
- DepsTracker::simplifyDependencies(ops, DepsTracker::TruncateToRootLevel::yes));
- }
- return topLevelDeps;
-}
-
-/**
- * Returns whether or not there is an expression in the projection which depends on 'field' other
- * than the expression which computes 'field'. For example, given field "a" and projection
- * {a: "$b", c: {$sum: ["$a", 5]}}, return true. Given field "a" and projection
- * {a: {$sum: ["$a", 5]}, c: "$b"}, return false. 'field' should be a top level path.
- */
-bool computedExprDependsOnField(const std::vector<OrderedPathSet>& topLevelDeps,
- const std::string& field,
- const size_t fieldIndex) {
- for (size_t i = 0; i < topLevelDeps.size(); i++) {
- if (i != fieldIndex && topLevelDeps[i].count(field) > 0) {
- return true;
- }
- }
- return false;
-}
} // namespace
std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInProject(
@@ -158,14 +109,8 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInProject(
return {BSONObj{}, false};
}
- boost::optional<std::vector<OrderedPathSet>> topLevelDeps =
- getTopLevelDeps(_orderToProcessAdditionsAndChildren, _expressions, _children);
-
- // If one of the expression requires the whole document, then we should not extract the
- // projection and topLevelDeps will not hold any field names.
- if (!topLevelDeps) {
- return {BSONObj{}, false};
- }
+ DepsTracker allDeps;
+ reportDependencies(&allDeps);
// Auxiliary vector with extracted computed projections: <name, expression, replacement
// strategy>. If the replacement strategy flag is true, the expression is replaced with a
@@ -173,15 +118,19 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInProject(
std::vector<std::tuple<StringData, boost::intrusive_ptr<Expression>, bool>>
addFieldsExpressions;
bool replaceWithProjField = true;
- for (size_t i = 0; i < _orderToProcessAdditionsAndChildren.size(); i++) {
- auto&& field = _orderToProcessAdditionsAndChildren[i];
-
+ for (auto&& field : _orderToProcessAdditionsAndChildren) {
if (reservedNames.count(field) > 0) {
// Do not pushdown computed projection with reserved name.
replaceWithProjField = false;
continue;
}
-
+ if (allDeps.fields.count(field) > 0) {
+ // Do not extract a computed projection if its name is the same as a dependent field. If
+ // the extracted $addFields were to be placed before this projection, the dependency
+ // with the common name would be shadowed by the computed projection.
+ replaceWithProjField = false;
+ continue;
+ }
auto expressionIt = _expressions.find(field);
if (expressionIt == _expressions.end()) {
// After seeing the first dotted path expression we need to replace computed
@@ -189,17 +138,13 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInProject(
replaceWithProjField = false;
continue;
}
+ DepsTracker deps;
+ expressionIt->second->addDependencies(&deps);
+ auto topLevelFieldNames =
+ deps.toProjectionWithoutMetadata(DepsTracker::TruncateToRootLevel::yes)
+ .getFieldNames<std::set<std::string>>();
+ topLevelFieldNames.erase("_id");
- // Do not extract a computed projection if it is computing a value that other fields in the
- // same projection depend on. If the extracted $addFields were to be placed before this
- // projection, the dependency with the common name would be shadowed by the computed
- // projection.
- if (computedExprDependsOnField(topLevelDeps.get(), field, i)) {
- replaceWithProjField = false;
- continue;
- }
-
- const auto& topLevelFieldNames = topLevelDeps.get()[i];
if (topLevelFieldNames.size() == 1 && topLevelFieldNames.count(oldName.toString()) == 1) {
// Substitute newName for oldName in the expression.
StringMap<std::string> renames;
@@ -219,12 +164,11 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInProject(
for (const auto& expressionSpec : addFieldsExpressions) {
auto&& fieldName = std::get<0>(expressionSpec).toString();
auto oldExpr = std::get<1>(expressionSpec);
- oldExpr->serialize().addToBsonObj(&bb, fieldName);
+ oldExpr->serialize(false).addToBsonObj(&bb, fieldName);
if (std::get<2>(expressionSpec)) {
// Replace the expression with an inclusion projected field.
- auto it = _projectedFields.insert(_projectedFields.end(), fieldName);
- _projectedFieldsSet.insert(StringData(*it));
+ _projectedFields.insert(fieldName);
_expressions.erase(fieldName);
// Only computed projections at the beginning of the list were marked to become
// projected fields. The new projected field is at the beginning of the
@@ -253,40 +197,35 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInAddFields(
return {BSONObj{}, false};
}
- boost::optional<std::vector<OrderedPathSet>> topLevelDeps =
- getTopLevelDeps(_orderToProcessAdditionsAndChildren, _expressions, _children);
-
- // If one of the expression requires the whole document, then we should not extract the
- // projection and topLevelDeps will not hold any field names.
- if (!topLevelDeps) {
- return {BSONObj{}, false};
- }
+ DepsTracker allDeps;
+ reportDependencies(&allDeps);
// Auxiliary vector with extracted computed projections: <name, expression>.
// To preserve the original fields order, only projections at the beginning of the
// _orderToProcessAdditionsAndChildren list can be extracted for pushdown.
std::vector<std::pair<StringData, boost::intrusive_ptr<Expression>>> addFieldsExpressions;
- for (size_t i = 0; i < _orderToProcessAdditionsAndChildren.size(); i++) {
- auto&& field = _orderToProcessAdditionsAndChildren[i];
+ for (auto&& field : _orderToProcessAdditionsAndChildren) {
// Do not extract for pushdown computed projection with reserved name.
if (reservedNames.count(field) > 0) {
break;
}
-
- auto expressionIt = _expressions.find(field);
- if (expressionIt == _expressions.end()) {
+ if (allDeps.fields.count(field) > 0) {
+ // Do not extract a computed projection if its name is the same as a dependent field. If
+ // the extracted $addFields were to be placed before this $addFields, the dependency
+ // with the common name would be shadowed by the computed projection.
break;
}
-
- // Do not extract a computed projection if it is computing a value that other fields in the
- // same projection depend on. If the extracted $addFields were to be placed before this
- // projection, the dependency with the common name would be shadowed by the computed
- // projection.
- if (computedExprDependsOnField(topLevelDeps.get(), field, i)) {
+ auto expressionIt = _expressions.find(field);
+ if (expressionIt == _expressions.end()) {
break;
}
+ DepsTracker deps;
+ expressionIt->second->addDependencies(&deps);
+ auto topLevelFieldNames =
+ deps.toProjectionWithoutMetadata(DepsTracker::TruncateToRootLevel::yes)
+ .getFieldNames<std::set<std::string>>();
+ topLevelFieldNames.erase("_id");
- auto& topLevelFieldNames = topLevelDeps.get()[i];
if (topLevelFieldNames.size() == 1 && topLevelFieldNames.count(oldName.toString()) == 1) {
// Substitute newName for oldName in the expression.
StringMap<std::string> renames;
@@ -303,7 +242,7 @@ std::pair<BSONObj, bool> InclusionNode::extractComputedProjectionsInAddFields(
for (const auto& expressionSpec : addFieldsExpressions) {
auto&& fieldName = expressionSpec.first.toString();
auto expr = expressionSpec.second;
- expr->serialize().addToBsonObj(&bb, fieldName);
+ expr->serialize(false).addToBsonObj(&bb, fieldName);
// Remove the expression from this inclusion node.
_expressions.erase(fieldName);
diff --git a/src/mongo/db/exec/inclusion_projection_executor.h b/src/mongo/db/exec/inclusion_projection_executor.h
index 7f505f54797..a0429ff924f 100644
--- a/src/mongo/db/exec/inclusion_projection_executor.h
+++ b/src/mongo/db/exec/inclusion_projection_executor.h
@@ -133,9 +133,6 @@ protected:
Value transformSkippedValueForOutput(const Value& value) const final {
return Value();
}
- bool isIncluded() const final {
- return true;
- }
};
/**
@@ -173,24 +170,19 @@ private:
*/
class InclusionProjectionExecutor : public ProjectionExecutor {
public:
- InclusionProjectionExecutor(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies,
- std::unique_ptr<InclusionNode> root,
- boost::optional<projection_ast::ProjectionPathASTNode> proj = boost::none)
- : ProjectionExecutor(expCtx, policies, proj), _root(std::move(root)) {}
-
- InclusionProjectionExecutor(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies,
- bool allowFastPath = false,
- boost::optional<projection_ast::ProjectionPathASTNode> proj = boost::none)
+ InclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies,
+ std::unique_ptr<InclusionNode> root)
+ : ProjectionExecutor(expCtx, policies), _root(std::move(root)) {}
+
+ InclusionProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ ProjectionPolicies policies,
+ bool allowFastPath = false)
: InclusionProjectionExecutor(
expCtx,
policies,
allowFastPath ? std::make_unique<FastPathEligibleInclusionNode>(policies)
- : std::make_unique<InclusionNode>(policies),
- proj) {}
+ : std::make_unique<InclusionNode>(policies)) {}
TransformerType getType() const final {
return TransformerType::kInclusionProjection;
@@ -207,17 +199,16 @@ public:
/**
* Serialize the projection.
*/
- Document serializeTransformation(boost::optional<ExplainOptions::Verbosity> explain,
- const SerializationOptions& options = {}) const final {
+ Document serializeTransformation(
+ boost::optional<ExplainOptions::Verbosity> explain) const final {
MutableDocument output;
// The InclusionNode tree in '_root' will always have a top-level _id node if _id is to be
// included. If the _id node is not present, then explicitly set {_id: false} to avoid
// ambiguity in the expected behavior of the serialized projection.
- _root->serialize(explain, &output, options);
- auto idFieldName = options.serializeFieldPath("_id");
- if (output.peek()[idFieldName].missing()) {
- output.addField(idFieldName, Value{false});
+ _root->serialize(explain, &output);
+ if (output.peek()["_id"].missing()) {
+ output.addField("_id", Value{false});
}
return output.freeze();
@@ -246,10 +237,10 @@ public:
return {DocumentSource::GetModPathsReturn::Type::kAllPaths, {}, {}};
}
- OrderedPathSet preservedPaths;
+ std::set<std::string> preservedPaths;
_root->reportProjectedPaths(&preservedPaths);
- OrderedPathSet computedPaths;
+ std::set<std::string> computedPaths;
StringMap<std::string> renamedPaths;
_root->reportComputedPaths(&computedPaths, &renamedPaths);
diff --git a/src/mongo/db/exec/inclusion_projection_executor_test.cpp b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
index b31899043ca..84ed31a9bb4 100644
--- a/src/mongo/db/exec/inclusion_projection_executor_test.cpp
+++ b/src/mongo/db/exec/inclusion_projection_executor_test.cpp
@@ -814,20 +814,18 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault,
"i: {$meta: 'recordId'}, "
"j: {$meta: 'indexKey'}, "
"k: {$meta: 'sortKey'}, "
- "l: {$meta: 'searchScoreDetails'}}, "
- "m: {$meta: 'vectorSearchScore'}"));
+ "l: {$meta: 'searchScoreDetails'}}"));
DepsTracker deps;
inclusion->addDependencies(&deps);
ASSERT_EQ(deps.fields.size(), 2UL);
- // We do not add the dependencies for searchScore, searchHighlights, searchScoreDetails, or
- // distance because those values are not stored in the collection (or in mongod at all).
+ // We do not add the dependencies for searchScore, searchHighlights, or searchScoreDetails
+ // because those values are not stored in the collection (or in mongod at all).
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScore]);
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchHighlights]);
ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kSearchScoreDetails]);
- ASSERT_FALSE(deps.metadataDeps()[DocumentMetadataFields::kVectorSearchScore]);
ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kTextScore]);
ASSERT_TRUE(deps.metadataDeps()[DocumentMetadataFields::kRandVal]);
@@ -849,8 +847,7 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldEvaluateMeta
"i: {$meta: 'recordId'}, "
"j: {$meta: 'indexKey'}, "
"k: {$meta: 'sortKey'}, "
- "l: {$meta: 'searchScoreDetails'}, "
- "m: {$meta: 'vectorSearchScore'}}"));
+ "l: {$meta: 'searchScoreDetails'}}"));
MutableDocument inputDocBuilder(Document{{"a", 1}});
inputDocBuilder.metadata().setTextScore(0.0);
@@ -864,7 +861,6 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldEvaluateMeta
inputDocBuilder.metadata().setSortKey(Value{Document{{"bar", 8}}}, true);
inputDocBuilder.metadata().setSearchScoreDetails(BSON("scoreDetails"
<< "foo"));
- inputDocBuilder.metadata().setVectorSearchScore(9.0);
Document inputDoc = inputDocBuilder.freeze();
auto result = inclusion->applyTransformation(inputDoc);
@@ -872,7 +868,7 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ShouldEvaluateMeta
ASSERT_DOCUMENT_EQ(result,
Document{fromjson("{a: 1, c: 0.0, d: 1.0, e: 2.0, f: 'foo', g: 3.0, "
"h: [4, 5], i: 6, j: {foo: 7}, k: [{bar: 8}], "
- "l: {scoreDetails: 'foo'}, m: 9.0}")});
+ "l: {scoreDetails: 'foo'}}")});
}
//
@@ -1094,62 +1090,6 @@ TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault,
ASSERT_DOCUMENT_EQ(expectedProjection, inclusion->serializeTransformation(boost::none));
}
-TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault,
- ExtractComputedProjectionInProjectShouldNotIncludeId) {
- auto inclusion = makeInclusionProjectionWithDefaultPolicies(
- BSON("a" << BSON("$sum" << BSON_ARRAY("$myMeta"
- << "$_id"))));
-
- auto r = static_cast<InclusionProjectionExecutor*>(inclusion.get())->getRoot();
- const std::set<StringData> reservedNames{};
- auto [addFields, deleteFlag] =
- r->extractComputedProjectionsInProject("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(addFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection = Document(fromjson("{_id: true, a: {$sum: ['$myMeta', '$_id']}}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, inclusion->serializeTransformation(boost::none));
-}
-
-TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault,
- ExtractComputedProjectionInProjectShouldNotHideDependentSubFields) {
- auto inclusion = makeInclusionProjectionWithDefaultPolicies(BSON("a"
- << "$myMeta"
- << "b"
- << "$a.x"));
-
- auto r = static_cast<InclusionProjectionExecutor*>(inclusion.get())->getRoot();
- const std::set<StringData> reservedNames{};
- auto [addFields, deleteFlag] =
- r->extractComputedProjectionsInProject("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(addFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection = Document(fromjson("{_id: true, a: '$myMeta', b: '$a.x'}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, inclusion->serializeTransformation(boost::none));
-}
-
-TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault,
- ExtractComputedProjectionInProjectShouldNotHideDependentSubFieldsWithDottedSibling) {
- auto inclusion = makeInclusionProjectionWithDefaultPolicies(BSON("a"
- << "$myMeta"
- << "c.b"
- << "$a.x"));
-
- auto r = static_cast<InclusionProjectionExecutor*>(inclusion.get())->getRoot();
- const std::set<StringData> reservedNames{};
- auto [addFields, deleteFlag] =
- r->extractComputedProjectionsInProject("myMeta", "meta", reservedNames);
-
- ASSERT_EQ(addFields.nFields(), 0);
- ASSERT_EQ(deleteFlag, false);
-
- auto expectedProjection = Document(fromjson("{_id: true, a: '$myMeta', c: {b: '$a.x'}}"));
- ASSERT_DOCUMENT_EQ(expectedProjection, inclusion->serializeTransformation(boost::none));
-}
-
TEST_F(InclusionProjectionExecutionTestWithFallBackToDefault, ApplyProjectionAfterSplit) {
auto inclusion = makeInclusionProjectionWithDefaultPolicies(
BSON("a" << true << "computedMeta1"
diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp
index c61fc452ec7..06399c4e33b 100644
--- a/src/mongo/db/exec/index_scan.cpp
+++ b/src/mongo/db/exec/index_scan.cpp
@@ -280,7 +280,9 @@ std::unique_ptr<PlanStageStats> IndexScan::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (nullptr != _filter) {
- _commonStats.filter = _filter->serialize();
+ BSONObjBuilder bob;
+ _filter->serialize(&bob);
+ _commonStats.filter = bob.obj();
}
// These specific stats fields never change.
diff --git a/src/mongo/db/exec/multi_plan.cpp b/src/mongo/db/exec/multi_plan.cpp
index 00254cef133..def29719b63 100644
--- a/src/mongo/db/exec/multi_plan.cpp
+++ b/src/mongo/db/exec/multi_plan.cpp
@@ -190,13 +190,14 @@ PlanStage::StageState MultiPlanStage::doWork(WorkingSetID* out) {
.getPlanCache()
->remove(plan_cache_key_factory::make<PlanCacheKey>(*_query, collection()));
- switchToBackupPlan();
+ _bestPlanIdx = _backupPlanIdx;
+ _backupPlanIdx = kNoSuchPlan;
return _candidates[_bestPlanIdx].root->work(out);
}
if (hasBackupPlan() && PlanStage::ADVANCED == state) {
LOGV2_DEBUG(20589, 5, "Best plan had a blocking stage, became unblocked");
- removeBackupPlan();
+ _backupPlanIdx = kNoSuchPlan;
}
return state;
@@ -293,7 +294,6 @@ Status MultiPlanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) {
plan_cache_util::updatePlanCache(
expCtx()->opCtx, collection(), _cachingMode, *_query, std::move(ranking), _candidates);
- removeRejectedPlans();
return Status::OK();
}
@@ -364,54 +364,6 @@ bool MultiPlanStage::workAllPlans(size_t numResults, PlanYieldPolicy* yieldPolic
return !doneWorking;
}
-void MultiPlanStage::removeRejectedPlans() {
- // Move the best plan and the backup plan to the front of 'children'.
- if (_bestPlanIdx != 0) {
- std::swap(_children[_bestPlanIdx], _children[0]);
- std::swap(_candidates[_bestPlanIdx], _candidates[0]);
- if (_backupPlanIdx == 0) {
- _backupPlanIdx = _bestPlanIdx;
- }
- _bestPlanIdx = 0;
- }
- size_t startIndex = 1;
- if (_backupPlanIdx != kNoSuchPlan) {
- if (_backupPlanIdx != 1) {
- std::swap(_children[_backupPlanIdx], _children[1]);
- std::swap(_candidates[_backupPlanIdx], _candidates[1]);
- _backupPlanIdx = 1;
- }
- startIndex = 2;
- }
-
- _rejected.reserve(_children.size() - startIndex);
- for (size_t i = startIndex; i < _children.size(); ++i) {
- rejectPlan(i);
- }
- _children.resize(startIndex);
-}
-
-void MultiPlanStage::switchToBackupPlan() {
- std::swap(_children[_backupPlanIdx], _children[_bestPlanIdx]);
- std::swap(_candidates[_backupPlanIdx], _candidates[_bestPlanIdx]);
- removeBackupPlan();
-}
-
-void MultiPlanStage::rejectPlan(size_t planIdx) {
- auto rejectedPlan = std::move(_children[planIdx]);
- if (opCtx() != nullptr) {
- rejectedPlan->saveState();
- rejectedPlan->detachFromOperationContext();
- }
- _rejected.emplace_back(std::move(rejectedPlan));
-}
-
-void MultiPlanStage::removeBackupPlan() {
- rejectPlan(_backupPlanIdx);
- _children.resize(1);
- _backupPlanIdx = kNoSuchPlan;
-}
-
bool MultiPlanStage::hasBackupPlan() const {
return kNoSuchPlan != _backupPlanIdx;
}
@@ -446,9 +398,6 @@ unique_ptr<PlanStageStats> MultiPlanStage::getStats() {
for (auto&& child : _children) {
ret->children.emplace_back(child->getStats());
}
- for (auto&& child : _rejected) {
- ret->children.emplace_back(child->getStats());
- }
return ret;
}
diff --git a/src/mongo/db/exec/multi_plan.h b/src/mongo/db/exec/multi_plan.h
index 12ab61659db..14d8ea413af 100644
--- a/src/mongo/db/exec/multi_plan.h
+++ b/src/mongo/db/exec/multi_plan.h
@@ -75,6 +75,7 @@ public:
std::unique_ptr<PlanStageStats> getStats() final;
+
const SpecificStats* getSpecificStats() const final;
boost::optional<double> getCandidateScore(size_t candidateIdx) const;
@@ -162,19 +163,6 @@ private:
*/
void tryYield(PlanYieldPolicy* yieldPolicy);
- /**
- * Deletes all children, except for best and backup plans.
- *
- * This is necessary to release any resources that rejected plans might have.
- * For example, if multi-update can be done by scanning several indexes,
- * it will be slowed down by rejected index scans because of index cursors
- * that need to be reopeneed after every update.
- */
- void removeRejectedPlans();
- void rejectPlan(size_t planIdx);
- void switchToBackupPlan();
- void removeBackupPlan();
-
static const int kNoSuchPlan = -1;
// Describes the cases in which we should write an entry for the winning plan to the plan cache.
@@ -190,9 +178,6 @@ private:
// one-to-one with _candidates.
std::vector<plan_ranker::CandidatePlan> _candidates;
- // Rejected plans in saved and detached state.
- std::vector<std::unique_ptr<PlanStage>> _rejected;
-
// index into _candidates, of the winner of the plan competition
// uses -1 / kNoSuchPlan when best plan is not (yet) known
int _bestPlanIdx;
diff --git a/src/mongo/db/exec/or.cpp b/src/mongo/db/exec/or.cpp
index 078765ffc84..ec0d680ac37 100644
--- a/src/mongo/db/exec/or.cpp
+++ b/src/mongo/db/exec/or.cpp
@@ -122,8 +122,10 @@ unique_ptr<PlanStageStats> OrStage::getStats() {
_commonStats.isEOF = isEOF();
// Add a BSON representation of the filter to the stats tree, if there is one.
- if (_filter) {
- _commonStats.filter = _filter->serialize();
+ if (nullptr != _filter) {
+ BSONObjBuilder bob;
+ _filter->serialize(&bob);
+ _commonStats.filter = bob.obj();
}
unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_OR);
diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h
index 236792ce64a..00e7fb33dbc 100644
--- a/src/mongo/db/exec/projection.h
+++ b/src/mongo/db/exec/projection.h
@@ -33,7 +33,6 @@
#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
-#include "mongo/db/query/projection.h"
#include "mongo/db/query/projection_ast.h"
#include "mongo/db/record_id.h"
diff --git a/src/mongo/db/exec/projection_executor.h b/src/mongo/db/exec/projection_executor.h
index 449ef8c3a7b..ca8e0d3990c 100644
--- a/src/mongo/db/exec/projection_executor.h
+++ b/src/mongo/db/exec/projection_executor.h
@@ -38,7 +38,6 @@
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/transformer_interface.h"
-#include "mongo/db/query/projection_ast.h"
#include "mongo/db/query/projection_policies.h"
namespace mongo::projection_executor {
@@ -98,20 +97,10 @@ public:
*/
virtual boost::optional<std::set<FieldRef>> extractExhaustivePaths() const = 0;
- /**
- * The query shape is made by serializing the first parsed representation of the query, which in
- * the case of $project queries is a projection_ast::Projection. The ProjectionExecutor, holds
- * onto the root node of the AST for only $project queries, so that the first parsed
- * representation is accessible at serialization.
- */
- boost::optional<projection_ast::ProjectionPathASTNode> projection = boost::none;
-
protected:
ProjectionExecutor(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- ProjectionPolicies policies,
- boost::optional<projection_ast::ProjectionPathASTNode> proj = boost::none)
- : projection(proj),
- _expCtx(expCtx),
+ ProjectionPolicies policies)
+ : _expCtx(expCtx),
_policies(policies),
_projectionPostImageVarId{
_expCtx->variablesParseState.defineVariable(kProjectionPostImageVarName)} {}
diff --git a/src/mongo/db/exec/projection_executor_builder.cpp b/src/mongo/db/exec/projection_executor_builder.cpp
index f5e94be3f78..1b712685ea9 100644
--- a/src/mongo/db/exec/projection_executor_builder.cpp
+++ b/src/mongo/db/exec/projection_executor_builder.cpp
@@ -251,7 +251,7 @@ auto buildProjectionExecutor(boost::intrusive_ptr<ExpressionContext> expCtx,
const ProjectionPolicies policies,
const BuilderParamsBitSet params) {
ProjectionExecutorVisitorContext<Executor> context{
- {std::make_unique<Executor>(expCtx, policies, params[kAllowFastPath], *root), expCtx}};
+ {std::make_unique<Executor>(expCtx, policies, params[kAllowFastPath]), expCtx}};
ProjectionExecutorVisitor<Executor> executorVisitor{&context};
projection_ast::PathTrackingWalker walker{&context, {&executorVisitor}, {}};
tree_walker::walk<true, projection_ast::ASTNode>(root, &walker);
diff --git a/src/mongo/db/exec/projection_executor_builder.h b/src/mongo/db/exec/projection_executor_builder.h
index 12f683a1cba..476f2b63a25 100644
--- a/src/mongo/db/exec/projection_executor_builder.h
+++ b/src/mongo/db/exec/projection_executor_builder.h
@@ -32,7 +32,6 @@
#include <bitset>
#include "mongo/db/exec/projection_executor.h"
-#include "mongo/db/query/projection.h"
#include "mongo/db/query/projection_ast.h"
namespace mongo::projection_executor {
diff --git a/src/mongo/db/exec/projection_executor_redaction_test.cpp b/src/mongo/db/exec/projection_executor_redaction_test.cpp
deleted file mode 100644
index 70eb59855bb..00000000000
--- a/src/mongo/db/exec/projection_executor_redaction_test.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-/**
- * Copyright (C) 2023-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "document_value/document_value_test_util.h"
-#include "mongo/db/exec/projection_executor.h"
-#include "mongo/db/exec/projection_executor_builder.h"
-#include "mongo/db/matcher/expression_parser.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/projection_ast_util.h"
-#include "mongo/db/query/projection_parser.h"
-#include "mongo/db/query/projection_policies.h"
-#include "mongo/db/query/query_shape/serialization_options.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-std::unique_ptr<projection_executor::ProjectionExecutor> compileProjection(BSONObj proj) {
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- auto policies = ProjectionPolicies::findProjectionPolicies();
- auto ast = projection_ast::parseAndAnalyze(expCtx, proj, policies);
- return projection_executor::buildProjectionExecutor(
- expCtx, &ast, policies, projection_executor::kDefaultBuilderParams);
-}
-std::unique_ptr<projection_executor::ProjectionExecutor> compileProjection(BSONObj proj,
- BSONObj query) {
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- auto match = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
- auto policies = ProjectionPolicies::findProjectionPolicies();
- auto ast = projection_ast::parseAndAnalyze(expCtx, proj, match.get(), query, policies);
- auto exec = projection_executor::buildProjectionExecutor(
- expCtx, &ast, policies, projection_executor::kDefaultBuilderParams);
- return exec;
-}
-
-TEST(Redaction, ProjectionTest) {
- SerializationOptions options = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST;
- auto redactProj = [&](std::string obj) {
- return compileProjection(fromjson(obj))->serializeTransformation(boost::none, options);
- };
-
- /// Inclusion projections
-
- // Simple single inclusion
- auto actual = redactProj("{a: 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":true})",
- actual);
-
- actual = redactProj("{a: true}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":true})",
- actual);
-
- // Dotted path
- actual = redactProj("{\"a.b\": 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":{"HASH<b>":true}})",
- actual);
-
- // Two fields
- actual = redactProj("{a: 1, b: 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":true,"HASH<b>":true})",
- actual);
-
- // Explicit _id: 1
- actual = redactProj("{b: 1, _id: 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<b>":true})",
- actual);
-
- // Two nested fields
- actual = redactProj("{\"b.d\": 1, \"b.c\": 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<b>":{"HASH<d>":true,"HASH<c>":true}})",
- actual);
-
- actual = redactProj("{\"b.d\": 1, a: 1, \"b.c\": 1}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "HASH<_id>": true,
- "HASH<a>": true,
- "HASH<b>": {
- "HASH<d>": true,
- "HASH<c>": true
- }
- })",
- actual);
-
- /// Exclusion projections
-
- // Simple single exclusion
- actual = redactProj("{a: 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<a>":false,"HASH<_id>":true})",
- actual);
-
- // Dotted path
- actual = redactProj("{\"a.b\": 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<a>":{"HASH<b>":false},"HASH<_id>":true})",
- actual);
-
- // Two fields
- actual = redactProj("{a: 0, b: 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<a>":false,"HASH<b>":false,"HASH<_id>":true})",
- actual);
-
- // Explicit _id: 0
- actual = redactProj("{b: 0, _id: 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":false,"HASH<b>":false})",
- actual);
-
- // Two nested fields
- actual = redactProj("{\"b.d\": 0, \"b.c\": 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<b>":{"HASH<d>":false,"HASH<c>":false},"HASH<_id>":true})",
- actual);
-
- actual = redactProj("{\"b.d\": 0, a: 0, \"b.c\": 0}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "HASH<a>": false,
- "HASH<b>": {
- "HASH<d>": false,
- "HASH<c>": false
- },
- "HASH<_id>": true
- })",
- actual);
-
- /// Add fields projection
- actual = redactProj("{a: \"hi\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":"?string"})",
- actual);
-
- actual = redactProj("{a: '$field'}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":"$HASH<field>"})",
- actual);
-
- // Dotted path
- actual = redactProj("{\"a.b\": \"hi\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":{"HASH<b>":"?string"}})",
- actual);
-
- // Two fields
- actual = redactProj("{a: \"hi\", b: \"hello\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<a>":"?string","HASH<b>":"?string"})",
- actual);
-
- // Explicit _id: 0
- actual = redactProj("{b: \"hi\", _id: \"hey\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<b>":"?string","HASH<_id>":"?string"})",
- actual);
-
- // Two nested fields
- actual = redactProj("{\"b.d\": \"hello\", \"b.c\": \"world\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"HASH<_id>":true,"HASH<b>":{"HASH<d>":"?string","HASH<c>":"?string"}})",
- actual);
-
- actual = redactProj("{\"b.d\": \"hello\", a: \"world\", \"b.c\": \"mongodb\"}");
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "HASH<_id>": true,
- "HASH<b>": {
- "HASH<d>": "?string",
- "HASH<c>": "?string"
- },
- "HASH<a>": "?string"
- })",
- actual);
-}
-} // namespace
-} // namespace mongo
diff --git a/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp b/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp
index fc1e74a0c2e..bdf5dea8241 100644
--- a/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp
+++ b/src/mongo/db/exec/projection_executor_wildcard_access_test.cpp
@@ -74,7 +74,7 @@ std::unique_ptr<ProjectionExecutor> makeProjectionWithDefaultIdExclusionAndNeste
return createProjectionExecutor(projSpec, policies);
}
-std::set<FieldRef> toFieldRefs(const OrderedPathSet& stringPaths) {
+std::set<FieldRef> toFieldRefs(const std::set<std::string>& stringPaths) {
std::set<FieldRef> fieldRefs;
std::transform(stringPaths.begin(),
stringPaths.end(),
diff --git a/src/mongo/db/exec/projection_node.cpp b/src/mongo/db/exec/projection_node.cpp
index 6b053c4593f..1ef569ecd75 100644
--- a/src/mongo/db/exec/projection_node.cpp
+++ b/src/mongo/db/exec/projection_node.cpp
@@ -48,8 +48,7 @@ void ProjectionNode::addProjectionForPath(const FieldPath& path) {
void ProjectionNode::_addProjectionForPath(const FieldPath& path) {
makeOptimizationsStale();
if (path.getPathLength() == 1) {
- auto it = _projectedFields.insert(_projectedFields.end(), path.fullPath());
- _projectedFieldsSet.insert(StringData(*it));
+ _projectedFields.insert(path.fullPath());
return;
}
// FieldPath can't be empty, so it is safe to obtain the first path component here.
@@ -139,18 +138,12 @@ void ProjectionNode::applyProjections(const Document& inputDoc, MutableDocument*
auto it = inputDoc.fieldIterator();
size_t projectedFields = 0;
- bool isIncl = isIncluded();
-
while (it.more()) {
auto fieldName = it.fieldName();
- if (_projectedFieldsSet.find(fieldName) != _projectedFieldsSet.end()) {
- if (isIncl) {
- outputProjectedField(fieldName, it.next().second, outputDoc);
- } else {
- outputProjectedField(fieldName, Value(), outputDoc);
- it.advance();
- }
+ if (_projectedFields.find(fieldName) != _projectedFields.end()) {
+ outputProjectedField(
+ fieldName, applyLeafProjectionToValue(it.next().second), outputDoc);
++projectedFields;
} else if (auto childIt = _children.find(fieldName); childIt != _children.end()) {
outputProjectedField(
@@ -236,7 +229,7 @@ Value ProjectionNode::applyExpressionsToValue(const Document& root, Value inputV
}
}
-void ProjectionNode::reportProjectedPaths(OrderedPathSet* projectedPaths) const {
+void ProjectionNode::reportProjectedPaths(std::set<std::string>* projectedPaths) const {
for (auto&& projectedField : _projectedFields) {
projectedPaths->insert(FieldPath::getFullyQualifiedPath(_pathToNode, projectedField));
}
@@ -246,7 +239,7 @@ void ProjectionNode::reportProjectedPaths(OrderedPathSet* projectedPaths) const
}
}
-void ProjectionNode::reportComputedPaths(OrderedPathSet* computedPaths,
+void ProjectionNode::reportComputedPaths(std::set<std::string>* computedPaths,
StringMap<std::string>* renamedPaths) const {
for (auto&& computedPair : _expressions) {
// The expression's path is the concatenation of the path to this node, plus the field name
@@ -275,27 +268,25 @@ void ProjectionNode::optimize() {
_maxFieldsToProject = maxFieldsToProject();
}
-Document ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explain,
- const SerializationOptions& options) const {
+Document ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument outputDoc;
- serialize(explain, &outputDoc, options);
+ serialize(explain, &outputDoc);
return outputDoc.freeze();
}
void ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explain,
- MutableDocument* output,
- const SerializationOptions& options) const {
+ MutableDocument* output) const {
// Determine the boolean value for projected fields in the explain output.
- const bool projVal = isIncluded();
+ const bool projVal = !applyLeafProjectionToValue(Value(true)).missing();
// Always put "_id" first if it was projected (implicitly or explicitly).
- if (_projectedFieldsSet.find("_id") != _projectedFieldsSet.end()) {
- output->addField(options.serializeFieldPath("_id"), Value(projVal));
+ if (_projectedFields.find("_id") != _projectedFields.end()) {
+ output->addField("_id", Value(projVal));
}
for (auto&& projectedField : _projectedFields) {
if (projectedField != "_id") {
- output->addField(options.serializeFieldPathFromString(projectedField), Value(projVal));
+ output->addField(projectedField, Value(projVal));
}
}
@@ -303,14 +294,13 @@ void ProjectionNode::serialize(boost::optional<ExplainOptions::Verbosity> explai
auto childIt = _children.find(field);
if (childIt != _children.end()) {
MutableDocument subDoc;
- childIt->second->serialize(explain, &subDoc, options);
- output->addField(options.serializeFieldPathFromString(field), subDoc.freezeToValue());
+ childIt->second->serialize(explain, &subDoc);
+ output->addField(field, subDoc.freezeToValue());
} else {
invariant(_policies.computedFieldsPolicy == ComputedFieldsPolicy::kAllowComputedFields);
auto expressionIt = _expressions.find(field);
invariant(expressionIt != _expressions.end());
- output->addField(options.serializeFieldPathFromString(field),
- expressionIt->second->serialize(options));
+ output->addField(field, expressionIt->second->serialize(static_cast<bool>(explain)));
}
}
}
diff --git a/src/mongo/db/exec/projection_node.h b/src/mongo/db/exec/projection_node.h
index cd744ef4cac..2a587580330 100644
--- a/src/mongo/db/exec/projection_node.h
+++ b/src/mongo/db/exec/projection_node.h
@@ -29,9 +29,8 @@
#pragma once
-#include <list>
-
#include "mongo/db/exec/projection_executor.h"
+
#include "mongo/db/query/projection_policies.h"
namespace mongo::projection_executor {
@@ -103,7 +102,7 @@ public:
/**
* Recursively report all paths that are referenced by this projection.
*/
- void reportProjectedPaths(OrderedPathSet* preservedPaths) const;
+ void reportProjectedPaths(std::set<std::string>* preservedPaths) const;
/**
* Return an optional number, x, which indicates that it is safe to stop reading the document
@@ -120,7 +119,7 @@ public:
* 'renamedPaths'. Each entry in 'renamedPaths' maps from the path's new name to its old name
* prior to application of this projection.
*/
- void reportComputedPaths(OrderedPathSet* computedPaths,
+ void reportComputedPaths(std::set<std::string>* computedPaths,
StringMap<std::string>* renamedPaths) const;
const std::string& getPath() const {
@@ -129,12 +128,10 @@ public:
void optimize();
- Document serialize(boost::optional<ExplainOptions::Verbosity> explain,
- const SerializationOptions& options) const;
+ Document serialize(boost::optional<ExplainOptions::Verbosity> explain) const;
void serialize(boost::optional<ExplainOptions::Verbosity> explain,
- MutableDocument* output,
- const SerializationOptions& options) const;
+ MutableDocument* output) const;
protected:
/**
@@ -163,19 +160,9 @@ protected:
// Writes the given value to the output doc, replacing the existing value of 'field' if present.
virtual void outputProjectedField(StringData field, Value val, MutableDocument* outDoc) const;
- // Used to determine if the node is an inclusion or exclusion node.
- virtual bool isIncluded() const = 0;
-
StringMap<std::unique_ptr<ProjectionNode>> _children;
StringMap<boost::intrusive_ptr<Expression>> _expressions;
-
- // List of the projected fields in the order in which they were specified.
- std::list<std::string> _projectedFields;
-
- // Set of projected fields. Note that the _projectedFields list actually owns the strings, and
- // this StringDataSet simply holds views of those strings.
- StringDataSet _projectedFieldsSet;
-
+ StringSet _projectedFields;
ProjectionPolicies _policies;
std::string _pathToNode;
diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript
index f8246baf556..b99625b0833 100644
--- a/src/mongo/db/exec/sbe/SConscript
+++ b/src/mongo/db/exec/sbe/SConscript
@@ -23,7 +23,7 @@ env.Library(
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/db/exec/js_function',
'$BUILD_DIR/mongo/db/fts/base_fts',
- '$BUILD_DIR/mongo/db/index/index_access_method',
+ '$BUILD_DIR/mongo/db/index/key_generator',
'$BUILD_DIR/mongo/db/query/collation/collator_interface',
'$BUILD_DIR/mongo/db/query/datetime/date_time_support',
'$BUILD_DIR/mongo/db/query/query_index_bounds',
@@ -59,9 +59,8 @@ sbeEnv.Library(
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/bson/dotted_path_support',
'$BUILD_DIR/mongo/db/sorter/sorter_idl',
- '$BUILD_DIR/mongo/db/sorter/sorter_stats',
- ],
-)
+ ]
+ )
sbeEnv.Library(
target='query_sbe_stages',
@@ -90,6 +89,7 @@ sbeEnv.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
+ '$BUILD_DIR/mongo/db/concurrency/write_conflict_exception',
'$BUILD_DIR/mongo/db/exec/js_function',
'$BUILD_DIR/mongo/db/exec/scoped_timer',
'$BUILD_DIR/mongo/db/query/plan_yield_policy',
@@ -106,7 +106,6 @@ sbeEnv.Library(
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/db/bson/dotted_path_support',
'$BUILD_DIR/mongo/db/sorter/sorter_idl',
- '$BUILD_DIR/mongo/db/sorter/sorter_stats',
'query_sbe',
'query_sbe_storage',
]
diff --git a/src/mongo/db/exec/sbe/abt/abt_lower.cpp b/src/mongo/db/exec/sbe/abt/abt_lower.cpp
index 7939d166a78..7da6a8e2cef 100644
--- a/src/mongo/db/exec/sbe/abt/abt_lower.cpp
+++ b/src/mongo/db/exec/sbe/abt/abt_lower.cpp
@@ -586,15 +586,14 @@ std::unique_ptr<sbe::PlanStage> SBENodeLowering::walk(const GroupByNode& n,
auto& names = binderAgg->names();
auto& exprs = refsAgg->nodes();
- sbe::SlotExprPairVector aggs;
- aggs.reserve(exprs.size());
+ sbe::value::SlotMap<std::unique_ptr<sbe::EExpression>> aggs;
for (size_t idx = 0; idx < exprs.size(); ++idx) {
auto expr = SBEExpressionLowering{_env, _slotMap}.optimize(exprs[idx]);
auto slot = _slotIdGenerator.generate();
_slotMap.emplace(names[idx], slot);
- aggs.push_back({slot, std::move(expr)});
+ aggs.emplace(slot, std::move(expr));
}
// TODO: use collator slot.
@@ -610,11 +609,6 @@ std::unique_ptr<sbe::PlanStage> SBENodeLowering::walk(const GroupByNode& n,
true /*optimizedClose*/,
collatorSlot,
false /*allowDiskUse*/,
- // Since we are always disallowing disk use for this stage,
- // we need not provide merging expressions. Once spilling
- // is permitted here, we will need to generate merging
- // expressions during lowering.
- sbe::makeSlotExprPairVec() /*mergingExprs*/,
planNodeId);
}
@@ -1011,7 +1005,6 @@ std::unique_ptr<sbe::PlanStage> SBENodeLowering::walk(const IndexScanNode& n, co
resultSlot,
ridSlot,
boost::none,
- boost::none,
indexKeysToInclude,
vars,
seekKeySlotLower,
diff --git a/src/mongo/db/exec/sbe/expression_test_base.h b/src/mongo/db/exec/sbe/expression_test_base.h
index bc5121407f9..72d9820d760 100644
--- a/src/mongo/db/exec/sbe/expression_test_base.h
+++ b/src/mongo/db/exec/sbe/expression_test_base.h
@@ -69,24 +69,6 @@ protected:
}
/**
- * Compiles 'expr' to bytecode when 'expr' is computing an aggregate. The current aggregate
- * value can be read out of the provided 'aggAccessor'.
- *
- * Note that when actually executing the resulting bytecode, the caller is responsible for
- * setting the value of 'aggAccessor' to the new resulting aggregate value.
- */
- std::unique_ptr<vm::CodeFragment> compileAggExpression(const EExpression& expr,
- value::SlotAccessor* aggAccessor) {
- ON_BLOCK_EXIT([this] {
- _ctx.aggExpression = false;
- _ctx.accumulator = nullptr;
- });
- _ctx.aggExpression = true;
- _ctx.accumulator = aggAccessor;
- return expr.compile(_ctx);
- }
-
- /**
* The caller takes ownership of the Value returned by this function and must call
* 'releaseValue()' on it. The preferred way to ensure the Value is properly released is to
* immediately store it in a ValueGuard.
diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp
index 65421e8b373..3a2b20a4657 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.cpp
+++ b/src/mongo/db/exec/sbe/expressions/expression.cpp
@@ -429,8 +429,6 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
BuiltinFn{[](size_t n) { return n > 0; }, vm::Builtin::doubleDoubleSum, false}},
{"aggDoubleDoubleSum",
BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::aggDoubleDoubleSum, true}},
- {"aggMergeDoubleDoubleSums",
- BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::aggMergeDoubleDoubleSums, true}},
{"doubleDoubleSumFinalize",
BuiltinFn{[](size_t n) { return n > 0; }, vm::Builtin::doubleDoubleSumFinalize, false}},
{"doubleDoubleMergeSumFinalize",
@@ -438,8 +436,6 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
{"doubleDoublePartialSumFinalize",
BuiltinFn{[](size_t n) { return n > 0; }, vm::Builtin::doubleDoublePartialSumFinalize, false}},
{"aggStdDev", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::aggStdDev, true}},
- {"aggMergeStdDevs",
- BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::aggMergeStdDevs, true}},
{"stdDevPopFinalize",
BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::stdDevPopFinalize, false}},
{"stdDevSampFinalize",
@@ -471,10 +467,7 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
{"tan", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::tan, false}},
{"tanh", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::tanh, false}},
{"round", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::round, false}},
- {"concat", BuiltinFn{kAnyNumberOfArgs, vm::Builtin::concat, false}},
- {"concatArrays", BuiltinFn{kAnyNumberOfArgs, vm::Builtin::concatArrays, false}},
- {"aggConcatArraysCapped",
- BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::aggConcatArraysCapped, true}},
+ {"concat", BuiltinFn{[](size_t n) { return n > 0; }, vm::Builtin::concat, false}},
{"isMember", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::isMember, false}},
{"collIsMember", BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::collIsMember, false}},
{"indexOfBytes",
@@ -493,11 +486,6 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
BuiltinFn{[](size_t n) { return n >= 1; }, vm::Builtin::collSetIntersection, false}},
{"collSetDifference",
BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::collSetDifference, false}},
- {"aggSetUnion", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::aggSetUnion, true}},
- {"aggSetUnionCapped",
- BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::aggSetUnionCapped, true}},
- {"aggCollSetUnionCapped",
- BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::aggCollSetUnionCapped, true}},
{"runJsPredicate",
BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::runJsPredicate, false}},
{"regexCompile", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::regexCompile, false}},
diff --git a/src/mongo/db/exec/sbe/expressions/expression.h b/src/mongo/db/exec/sbe/expressions/expression.h
index d5a2c0fdf0e..2b9032f257b 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.h
+++ b/src/mongo/db/exec/sbe/expressions/expression.h
@@ -348,8 +348,6 @@ private:
std::string toString() const;
};
-using SlotExprPairVector = std::vector<std::pair<value::SlotId, std::unique_ptr<EExpression>>>;
-
template <typename T, typename... Args>
inline std::unique_ptr<EExpression> makeE(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
@@ -366,30 +364,20 @@ inline auto makeEs(Ts&&... pack) {
namespace detail {
// base case
-template <typename R>
-inline void makeSlotExprPairHelper(R& result,
- value::SlotId slot,
- std::unique_ptr<EExpression> expr) {
- if constexpr (std::is_same_v<R, value::SlotMap<std::unique_ptr<EExpression>>>) {
- result.emplace(slot, std::move(expr));
- } else {
- result.push_back({slot, std::move(expr)});
- }
+inline void makeEM_unwind(value::SlotMap<std::unique_ptr<EExpression>>& result,
+ value::SlotId slot,
+ std::unique_ptr<EExpression> expr) {
+ result.emplace(slot, std::move(expr));
}
// recursive case
-template <typename R, typename... Ts>
-inline void makeSlotExprPairHelper(R& result,
- value::SlotId slot,
- std::unique_ptr<EExpression> expr,
- Ts&&... rest) {
- if constexpr (std::is_same_v<R, value::SlotMap<std::unique_ptr<EExpression>>>) {
- result.emplace(slot, std::move(expr));
- } else {
- static_assert(std::is_same_v<R, SlotExprPairVector>);
- result.push_back({slot, std::move(expr)});
- }
- makeSlotExprPairHelper(result, std::forward<Ts>(rest)...);
+template <typename... Ts>
+inline void makeEM_unwind(value::SlotMap<std::unique_ptr<EExpression>>& result,
+ value::SlotId slot,
+ std::unique_ptr<EExpression> expr,
+ Ts&&... rest) {
+ result.emplace(slot, std::move(expr));
+ makeEM_unwind(result, std::forward<Ts>(rest)...);
}
} // namespace detail
@@ -398,7 +386,7 @@ auto makeEM(Ts&&... pack) {
value::SlotMap<std::unique_ptr<EExpression>> result;
if constexpr (sizeof...(pack) > 0) {
result.reserve(sizeof...(Ts) / 2);
- detail::makeSlotExprPairHelper(result, std::forward<Ts>(pack)...);
+ detail::makeEM_unwind(result, std::forward<Ts>(pack)...);
}
return result;
}
@@ -411,16 +399,6 @@ auto makeSV(Args&&... args) {
return v;
}
-template <typename... Ts>
-auto makeSlotExprPairVec(Ts&&... pack) {
- SlotExprPairVector v;
- if constexpr (sizeof...(pack) > 0) {
- v.reserve(sizeof...(Ts) / 2);
- detail::makeSlotExprPairHelper(v, std::forward<Ts>(pack)...);
- }
- return v;
-}
-
/**
* This is a constant expression. It assumes the ownership of the input constant.
*/
diff --git a/src/mongo/db/exec/sbe/expressions/sbe_set_expressions_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_set_expressions_test.cpp
index 279f7a287b4..30eb61a7b2d 100644
--- a/src/mongo/db/exec/sbe/expressions/sbe_set_expressions_test.cpp
+++ b/src/mongo/db/exec/sbe/expressions/sbe_set_expressions_test.cpp
@@ -28,7 +28,6 @@
*/
#include "mongo/db/exec/sbe/expression_test_base.h"
-#include "mongo/db/query/sbe_stage_builder_helpers.h"
namespace mongo::sbe {
@@ -94,35 +93,6 @@ TEST_F(SBEBuiltinSetOpTest, ReturnsNothingSetUnion) {
runAndAssertNothing(compiledExpr.get());
}
-TEST_F(SBEBuiltinSetOpTest, AggSetUnion) {
- value::OwnedValueAccessor aggAccessor, inputAccessor;
- auto inputSlot = bindAccessor(&inputAccessor);
- auto setUnionExpr =
- stage_builder::makeFunction("aggSetUnion", stage_builder::makeVariable(inputSlot));
- auto compiledExpr = compileAggExpression(*setUnionExpr, &aggAccessor);
-
- auto [arrTag1, arrVal1] = makeArray(BSON_ARRAY(1 << 2));
- inputAccessor.reset(arrTag1, arrVal1);
- auto [resTag1, resVal1] = makeArraySet(BSON_ARRAY(1 << 2));
- runAndAssertExpression(compiledExpr.get(), {resTag1, resVal1});
- aggAccessor.reset(resTag1, resVal1);
-
- auto [arrTag2, arrVal2] = makeArraySet(BSON_ARRAY(1 << 3 << 2 << 6));
- inputAccessor.reset(arrTag2, arrVal2);
- auto [resTag2, resVal2] = makeArraySet(BSON_ARRAY(1 << 2 << 3 << 6));
- runAndAssertExpression(compiledExpr.get(), {resTag2, resVal2});
- aggAccessor.reset(resTag2, resVal2);
-
- auto [arrTag3, arrVal3] = makeArray(BSONArray{});
- inputAccessor.reset(arrTag3, arrVal3);
- auto [resTag3, resVal3] = makeArraySet(BSON_ARRAY(1 << 2 << 3 << 6));
- runAndAssertExpression(compiledExpr.get(), {resTag3, resVal3});
- aggAccessor.reset(resTag3, resVal3);
-
- inputAccessor.reset(value::TypeTags::Nothing, 0);
- runAndAssertNothing(compiledExpr.get());
-}
-
TEST_F(SBEBuiltinSetOpTest, ComputesSetIntersection) {
value::OwnedValueAccessor slotAccessor1, slotAccessor2;
auto arrSlot1 = bindAccessor(&slotAccessor1);
diff --git a/src/mongo/db/exec/sbe/sbe_hash_agg_test.cpp b/src/mongo/db/exec/sbe/sbe_hash_agg_test.cpp
index 89244fd4589..87fc8dbd1f2 100644
--- a/src/mongo/db/exec/sbe/sbe_hash_agg_test.cpp
+++ b/src/mongo/db/exec/sbe/sbe_hash_agg_test.cpp
@@ -69,23 +69,18 @@ void HashAggStageTest::performHashAggWithSpillChecking(
auto makeStageFn = [this, collatorSlot, shouldUseCollator, shouldSpill](
value::SlotId scanSlot, std::unique_ptr<PlanStage> scanStage) {
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction("sum",
- makeE<EConstant>(value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction("sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64,
+ value::bitcastFrom<int64_t>(1)))),
makeSV(),
true,
boost::optional<value::SlotId>{shouldUseCollator, collatorSlot},
shouldSpill,
- makeSlotExprPairVec(
- spillSlot,
- stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
return std::make_pair(countsSlot, std::move(hashAggStage));
@@ -171,21 +166,20 @@ TEST_F(HashAggStageTest, HashAggMinMaxTest) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(),
- makeSlotExprPairVec(minSlot,
- stage_builder::makeFunction("min", makeE<EVariable>(scanSlot)),
- maxSlot,
- stage_builder::makeFunction("max", makeE<EVariable>(scanSlot)),
- collMinSlot,
- stage_builder::makeFunction(
- "collMin", collExpr->clone(), makeE<EVariable>(scanSlot)),
- collMaxSlot,
- stage_builder::makeFunction(
- "collMax", collExpr->clone(), makeE<EVariable>(scanSlot))),
+ makeEM(minSlot,
+ stage_builder::makeFunction("min", makeE<EVariable>(scanSlot)),
+ maxSlot,
+ stage_builder::makeFunction("max", makeE<EVariable>(scanSlot)),
+ collMinSlot,
+ stage_builder::makeFunction(
+ "collMin", collExpr->clone(), makeE<EVariable>(scanSlot)),
+ collMaxSlot,
+ stage_builder::makeFunction(
+ "collMax", collExpr->clone(), makeE<EVariable>(scanSlot))),
makeSV(),
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec() /* mergingExprs */,
kEmptyPlanNodeId);
auto outSlot = generateSlotId();
@@ -236,15 +230,13 @@ TEST_F(HashAggStageTest, HashAggAddToSetTest) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(),
- makeSlotExprPairVec(hashAggSlot,
- stage_builder::makeFunction("collAddToSet",
- std::move(collExpr),
- makeE<EVariable>(scanSlot))),
+ makeEM(hashAggSlot,
+ stage_builder::makeFunction(
+ "collAddToSet", std::move(collExpr), makeE<EVariable>(scanSlot))),
makeSV(),
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec() /* mergingExprs */,
kEmptyPlanNodeId);
return std::make_pair(hashAggSlot, std::move(hashAggStage));
@@ -335,16 +327,14 @@ TEST_F(HashAggStageTest, HashAggSeekKeysTest) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction("sum",
- makeE<EConstant>(value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction("sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64,
+ value::bitcastFrom<int64_t>(1)))),
makeSV(seekSlot),
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec() /* mergingExprs */,
kEmptyPlanNodeId);
return std::make_pair(countsSlot, std::move(hashAggStage));
@@ -397,21 +387,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpill) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -434,7 +420,6 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpill) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_FALSE(stats->usedDisk);
- ASSERT_EQ(0, stats->numSpills);
ASSERT_EQ(0, stats->spilledRecords);
stage->close();
@@ -443,6 +428,7 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpill) {
TEST_F(HashAggStageTest, HashAggBasicCountSpill) {
// We estimate the size of result row like {int64, int64} at 50B. Set the memory threshold to
// 64B so that exactly one row fits in memory.
+ const int expectedRowsToFitInMemory = 1;
auto defaultInternalQuerySBEAggApproxMemoryUseInBytesBeforeSpill =
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.load();
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.store(64);
@@ -460,21 +446,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountSpill) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -497,14 +479,7 @@ TEST_F(HashAggStageTest, HashAggBasicCountSpill) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_TRUE(stats->usedDisk);
- // Memory usage is estimated only every two rows at the most frequent. Also, we only start
- // spilling after estimating that the memory budget is exceeded. These two factors result in
- // fewer expected spills than there are input records, even though only one record fits in
- // memory at a time.
- ASSERT_EQ(stats->numSpills, 3);
- // The input has one run of two consecutive values, so we expect to spill as many records as
- // there are input values minus one.
- ASSERT_EQ(stats->spilledRecords, 8);
+ ASSERT_EQ(results.size() - expectedRowsToFitInMemory, stats->spilledRecords);
stage->close();
}
@@ -538,21 +513,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillIfNoMemCheck) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -575,7 +546,6 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillIfNoMemCheck) {
// Check that it did not spill.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_FALSE(stats->usedDisk);
- ASSERT_EQ(0, stats->numSpills);
ASSERT_EQ(0, stats->spilledRecords);
stage->close();
@@ -584,6 +554,7 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillIfNoMemCheck) {
TEST_F(HashAggStageTest, HashAggBasicCountSpillDouble) {
// We estimate the size of result row like {double, int64} at 50B. Set the memory threshold to
// 64B so that exactly one row fits in memory.
+ const int expectedRowsToFitInMemory = 1;
auto defaultInternalQuerySBEAggApproxMemoryUseInBytesBeforeSpill =
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.load();
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.store(64);
@@ -601,21 +572,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountSpillDouble) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -638,14 +605,7 @@ TEST_F(HashAggStageTest, HashAggBasicCountSpillDouble) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_TRUE(stats->usedDisk);
- // Memory usage is estimated only every two rows at the most frequent. Also, we only start
- // spilling after estimating that the memory budget is exceeded. These two factors result in
- // fewer expected spills than there are input records, even though only one record fits in
- // memory at a time.
- ASSERT_EQ(stats->numSpills, 3);
- // The input has one run of two consecutive values, so we expect to spill as many records as
- // there are input values minus one.
- ASSERT_EQ(stats->spilledRecords, 8);
+ ASSERT_EQ(results.size() - expectedRowsToFitInMemory, stats->spilledRecords);
stage->close();
}
@@ -667,21 +627,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillWithNoGroupByDouble) {
// Build a HashAggStage, with an empty group by slot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -702,7 +658,6 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillWithNoGroupByDouble) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_FALSE(stats->usedDisk);
- ASSERT_EQ(0, stats->numSpills);
ASSERT_EQ(0, stats->spilledRecords);
stage->close();
@@ -711,6 +666,7 @@ TEST_F(HashAggStageTest, HashAggBasicCountNoSpillWithNoGroupByDouble) {
TEST_F(HashAggStageTest, HashAggMultipleAccSpill) {
// We estimate the size of result row like {double, int64} at 59B. Set the memory threshold to
// 128B so that two rows fit in memory.
+ const int expectedRowsToFitInMemory = 2;
auto defaultInternalQuerySBEAggApproxMemoryUseInBytesBeforeSpill =
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.load();
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.store(128);
@@ -729,27 +685,19 @@ TEST_F(HashAggStageTest, HashAggMultipleAccSpill) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
auto sumsSlot = generateSlotId();
- auto spillSlot1 = generateSlotId();
- auto spillSlot2 = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1))),
- sumsSlot,
- stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1))),
+ sumsSlot,
+ stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
makeSV(), // Seek slot
true,
boost::none,
true /* allowDiskUse */,
- makeSlotExprPairVec(
- spillSlot1,
- stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot1)),
- spillSlot2,
- stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot2))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -779,10 +727,7 @@ TEST_F(HashAggStageTest, HashAggMultipleAccSpill) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_TRUE(stats->usedDisk);
- ASSERT_EQ(stats->numSpills, 3);
- // The input has one run of two consecutive values, so we expect to spill as many records as
- // there are input values minus one.
- ASSERT_EQ(stats->spilledRecords, 8);
+ ASSERT_EQ(results.size() - expectedRowsToFitInMemory, stats->spilledRecords);
stage->close();
}
@@ -807,27 +752,19 @@ TEST_F(HashAggStageTest, HashAggMultipleAccSpillAllToDisk) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
auto sumsSlot = generateSlotId();
- auto spillSlot1 = generateSlotId();
- auto spillSlot2 = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1))),
- sumsSlot,
- stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1))),
+ sumsSlot,
+ stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
makeSV(), // Seek slot
true,
boost::none,
true, // allowDiskUse=true
- makeSlotExprPairVec(
- spillSlot1,
- stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot1)),
- spillSlot2,
- stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot2))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -857,9 +794,7 @@ TEST_F(HashAggStageTest, HashAggMultipleAccSpillAllToDisk) {
// Check that the spilling behavior matches the expected.
auto stats = static_cast<const HashAggStats*>(stage->getSpecificStats());
ASSERT_TRUE(stats->usedDisk);
- // We expect each incoming value to result in a spill of a single record.
- ASSERT_EQ(stats->numSpills, 9);
- ASSERT_EQ(stats->spilledRecords, 9);
+ ASSERT_EQ(results.size(), stats->spilledRecords);
stage->close();
}
@@ -896,18 +831,14 @@ TEST_F(HashAggStageTest, HashAggSum10Groups) {
// Build a HashAggStage, group by the scanSlot and compute a sum for each group.
auto sumsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(sumsSlot,
- stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
+ makeEM(sumsSlot, stage_builder::makeFunction("sum", makeE<EVariable>(scanSlot))),
makeSV(), // Seek slot
true,
boost::none,
true, // allowDiskUse=true
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
@@ -941,21 +872,17 @@ TEST_F(HashAggStageTest, HashAggBasicCountWithRecordIds) {
// Build a HashAggStage, group by the scanSlot and compute a simple count.
auto countsSlot = generateSlotId();
- auto spillSlot = generateSlotId();
auto stage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
makeSV(), // Seek slot
true,
boost::none,
true, // allowDiskUse=true
- makeSlotExprPairVec(
- spillSlot, stage_builder::makeFunction("sum", stage_builder::makeVariable(spillSlot))),
kEmptyPlanNodeId);
// Prepare the tree and get the 'SlotAccessor' for the output slot.
diff --git a/src/mongo/db/exec/sbe/sbe_plan_size_test.cpp b/src/mongo/db/exec/sbe/sbe_plan_size_test.cpp
index f62b8a6229c..191f0ffe562 100644
--- a/src/mongo/db/exec/sbe/sbe_plan_size_test.cpp
+++ b/src/mongo/db/exec/sbe/sbe_plan_size_test.cpp
@@ -139,12 +139,11 @@ TEST_F(PlanSizeTest, Filter) {
TEST_F(PlanSizeTest, HashAgg) {
auto stage = makeS<HashAggStage>(mockS(),
mockSV(),
- makeSlotExprPairVec(generateSlotId(), mockE()),
+ makeEM(generateSlotId(), mockE()),
makeSV(),
true,
generateSlotId(),
false,
- makeSlotExprPairVec(),
kEmptyPlanNodeId);
assertPlanSize(*stage);
}
@@ -169,7 +168,6 @@ TEST_F(PlanSizeTest, IndexScan) {
generateSlotId(),
generateSlotId(),
generateSlotId(),
- generateSlotId(),
IndexKeysInclusionSet(1),
mockSV(),
generateSlotId(),
diff --git a/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp b/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp
index 4f3e6d44372..77b1997bdee 100644
--- a/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp
+++ b/src/mongo/db/exec/sbe/sbe_plan_stage_test.cpp
@@ -66,12 +66,7 @@ PlanStageTestFixture::generateVirtualScanMulti(int32_t numSlots, const BSONArray
}
void PlanStageTestFixture::prepareTree(CompileCtx* ctx, PlanStage* root) {
- // We want to avoid recursive locking since this results in yield plans that don't yield when
- // they should.
- boost::optional<Lock::GlobalLock> globalLock;
- if (!opCtx()->lockState()->isLocked()) {
- globalLock.emplace(opCtx(), MODE_IS);
- }
+ Lock::GlobalLock globalLock{opCtx(), MODE_IS};
root->attachToOperationContext(opCtx());
root->prepare(*ctx);
root->open(false);
diff --git a/src/mongo/db/exec/sbe/sbe_trial_run_tracker_test.cpp b/src/mongo/db/exec/sbe/sbe_trial_run_tracker_test.cpp
index 57d3ff4e05f..54fadbe1f15 100644
--- a/src/mongo/db/exec/sbe/sbe_trial_run_tracker_test.cpp
+++ b/src/mongo/db/exec/sbe/sbe_trial_run_tracker_test.cpp
@@ -141,16 +141,14 @@ TEST_F(TrialRunTrackerTest, TrialEndsDuringOpenPhaseOfBlockingStage) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
- makeSV(), /* Seek slot */
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeSV(), // Seek slot
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec(), /* mergingExprs */
kEmptyPlanNodeId);
auto tracker = std::make_unique<TrialRunTracker>(numResultsLimit, size_t{0});
@@ -212,16 +210,14 @@ TEST_F(TrialRunTrackerTest, OnlyDeepestNestedBlockingStageHasTrialRunTracker) {
auto hashAggStage = makeS<HashAggStage>(
std::move(unionStage),
makeSV(unionSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction(
- "sum",
- makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
- makeSV(), /* Seek slot */
+ makeEM(countsSlot,
+ stage_builder::makeFunction(
+ "sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1)))),
+ makeSV(), // Seek slot
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec(), /* mergingExprs */
kEmptyPlanNodeId);
hashAggStage->prepare(*ctx);
@@ -281,16 +277,14 @@ TEST_F(TrialRunTrackerTest, SiblingBlockingStagesBothGetTrialRunTracker) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction("sum",
- makeE<EConstant>(value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(1)))),
- makeSV(), /* Seek slot */
+ makeEM(countsSlot,
+ stage_builder::makeFunction("sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64,
+ value::bitcastFrom<int64_t>(1)))),
+ makeSV(), // Seek slot
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec(), /* mergingExprs */
kEmptyPlanNodeId);
return std::make_pair(countsSlot, std::move(hashAggStage));
@@ -413,16 +407,14 @@ TEST_F(TrialRunTrackerTest, DisablingTrackingForAChildStagePreventsEarlyExit) {
auto hashAggStage = makeS<HashAggStage>(
std::move(scanStage),
makeSV(scanSlot),
- makeSlotExprPairVec(
- countsSlot,
- stage_builder::makeFunction("sum",
- makeE<EConstant>(value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(1)))),
- makeSV(), /* Seek slot */
+ makeEM(countsSlot,
+ stage_builder::makeFunction("sum",
+ makeE<EConstant>(value::TypeTags::NumberInt64,
+ value::bitcastFrom<int64_t>(1)))),
+ makeSV(), // Seek slot
true,
boost::none,
false /* allowDiskUse */,
- makeSlotExprPairVec(), /* mergingExprs */
kEmptyPlanNodeId);
return std::make_pair(countsSlot, std::move(hashAggStage));
diff --git a/src/mongo/db/exec/sbe/size_estimator.h b/src/mongo/db/exec/sbe/size_estimator.h
index bbb92328331..fb6684eea52 100644
--- a/src/mongo/db/exec/sbe/size_estimator.h
+++ b/src/mongo/db/exec/sbe/size_estimator.h
@@ -92,11 +92,6 @@ inline size_t estimate(const S& stats) {
return stats.estimateObjectSizeInBytes() - sizeof(S);
}
-template <typename A, typename B>
-inline size_t estimate(const std::pair<A, B>& pair) {
- return estimate(pair.first) + estimate(pair.second);
-}
-
// Calculate the size of the inlined vector's elements.
template <typename T, size_t N, typename A>
size_t estimate(const absl::InlinedVector<T, N, A>& vector) {
diff --git a/src/mongo/db/exec/sbe/stages/branch.cpp b/src/mongo/db/exec/sbe/stages/branch.cpp
index 1e8809f0858..bec12b12ee2 100644
--- a/src/mongo/db/exec/sbe/stages/branch.cpp
+++ b/src/mongo/db/exec/sbe/stages/branch.cpp
@@ -70,29 +70,31 @@ void BranchStage::prepare(CompileCtx& ctx) {
_children[0]->prepare(ctx);
_children[1]->prepare(ctx);
- // All of the slots listed in '_outputVals' must be unique.
for (size_t idx = 0; idx < _outputVals.size(); ++idx) {
- auto slot = _outputVals[idx];
- auto [_, inserted] = dupCheck.insert(slot);
- uassert(4822831, str::stream() << "duplicate field: " << slot, inserted);
- }
+ std::vector<value::SlotAccessor*> accessors;
+ accessors.reserve(2);
- for (size_t idx = 0; idx < _outputVals.size(); ++idx) {
- auto thenSlot = _inputThenVals[idx];
- auto elseSlot = _inputElseVals[idx];
+ {
+ auto slot = _inputThenVals[idx];
+ auto [it, inserted] = dupCheck.insert(slot);
+ uassert(4822829, str::stream() << "duplicate field: " << slot, inserted);
- // Slots listed in '_inputThenVals' and '_inputElseVals' may not appear in '_outputVals'.
- bool thenSlotFound = dupCheck.count(thenSlot);
- bool elseSlotFound = dupCheck.count(elseSlot);
- uassert(4822829, str::stream() << "duplicate field: " << thenSlot, !thenSlotFound);
- uassert(4822830, str::stream() << "duplicate field: " << elseSlot, !elseSlotFound);
+ accessors.emplace_back(_children[0]->getAccessor(ctx, slot));
+ }
+ {
+ auto slot = _inputElseVals[idx];
+ auto [it, inserted] = dupCheck.insert(slot);
+ uassert(4822830, str::stream() << "duplicate field: " << slot, inserted);
- std::vector<value::SlotAccessor*> accessors;
- accessors.reserve(2);
- accessors.emplace_back(_children[0]->getAccessor(ctx, thenSlot));
- accessors.emplace_back(_children[1]->getAccessor(ctx, elseSlot));
+ accessors.emplace_back(_children[1]->getAccessor(ctx, slot));
+ }
+ {
+ auto slot = _outputVals[idx];
+ auto [it, inserted] = dupCheck.insert(slot);
+ uassert(4822831, str::stream() << "duplicate field: " << slot, inserted);
- _outValueAccessors.emplace_back(value::SwitchAccessor{std::move(accessors)});
+ _outValueAccessors.emplace_back(value::SwitchAccessor{std::move(accessors)});
+ }
}
// compile filter
diff --git a/src/mongo/db/exec/sbe/stages/collection_helpers.h b/src/mongo/db/exec/sbe/stages/collection_helpers.h
index 7471763a44a..4116f2980ff 100644
--- a/src/mongo/db/exec/sbe/stages/collection_helpers.h
+++ b/src/mongo/db/exec/sbe/stages/collection_helpers.h
@@ -40,7 +40,6 @@ namespace mongo::sbe {
* A callback which gets called whenever a SCAN stage asks an underlying index scan for a result.
*/
using IndexKeyConsistencyCheckCallback = std::function<bool(OperationContext* opCtx,
- StringMap<const IndexCatalogEntry*>&,
value::SlotAccessor* snapshotIdAccessor,
value::SlotAccessor* indexIdAccessor,
value::SlotAccessor* indexKeyAccessor,
diff --git a/src/mongo/db/exec/sbe/stages/hash_agg.cpp b/src/mongo/db/exec/sbe/stages/hash_agg.cpp
index 2514dedf1b0..14f40afeaa9 100644
--- a/src/mongo/db/exec/sbe/stages/hash_agg.cpp
+++ b/src/mongo/db/exec/sbe/stages/hash_agg.cpp
@@ -27,67 +27,47 @@
* it in the license file.
*/
-#include "mongo/db/exec/sbe/stages/hash_agg.h"
+#include "mongo/platform/basic.h"
#include "mongo/db/concurrency/d_concurrency.h"
-#include "mongo/db/exec/sbe/size_estimator.h"
+#include "mongo/db/concurrency/write_conflict_exception.h"
+#include "mongo/db/exec/sbe/stages/hash_agg.h"
#include "mongo/db/exec/sbe/util/spilling.h"
-#include "mongo/db/stats/resource_consumption_metrics.h"
#include "mongo/db/storage/kv/kv_engine.h"
#include "mongo/db/storage/storage_engine.h"
#include "mongo/util/str.h"
+#include "mongo/db/exec/sbe/size_estimator.h"
+
namespace mongo {
namespace sbe {
HashAggStage::HashAggStage(std::unique_ptr<PlanStage> input,
value::SlotVector gbs,
- SlotExprPairVector aggs,
+ value::SlotMap<std::unique_ptr<EExpression>> aggs,
value::SlotVector seekKeysSlots,
bool optimizedClose,
boost::optional<value::SlotId> collatorSlot,
bool allowDiskUse,
- SlotExprPairVector mergingExprs,
- PlanNodeId planNodeId,
- bool forceIncreasedSpilling)
+ PlanNodeId planNodeId)
: PlanStage("group"_sd, planNodeId),
_gbs(std::move(gbs)),
_aggs(std::move(aggs)),
_collatorSlot(collatorSlot),
_allowDiskUse(allowDiskUse),
_seekKeysSlots(std::move(seekKeysSlots)),
- _optimizedClose(optimizedClose),
- _mergingExprs(std::move(mergingExprs)),
- _forceIncreasedSpilling(forceIncreasedSpilling) {
+ _optimizedClose(optimizedClose) {
_children.emplace_back(std::move(input));
invariant(_seekKeysSlots.empty() || _seekKeysSlots.size() == _gbs.size());
tassert(5843100,
"HashAgg stage was given optimizedClose=false and seek keys",
_seekKeysSlots.empty() || _optimizedClose);
-
- if (_allowDiskUse) {
- tassert(7039549,
- "disk use enabled for HashAggStage but incorrect number of merging expresssions",
- _aggs.size() == _mergingExprs.size());
- }
-
- if (_forceIncreasedSpilling) {
- tassert(7039554, "'forceIncreasedSpilling' set but disk use not allowed", _allowDiskUse);
- }
}
std::unique_ptr<PlanStage> HashAggStage::clone() const {
- SlotExprPairVector aggs;
- aggs.reserve(_aggs.size());
+ value::SlotMap<std::unique_ptr<EExpression>> aggs;
for (auto& [k, v] : _aggs) {
- aggs.push_back({k, v->clone()});
+ aggs.emplace(k, v->clone());
}
-
- SlotExprPairVector mergingExprsClone;
- mergingExprsClone.reserve(_mergingExprs.size());
- for (auto&& [k, v] : _mergingExprs) {
- mergingExprsClone.push_back({k, v->clone()});
- }
-
return std::make_unique<HashAggStage>(_children[0]->clone(),
_gbs,
std::move(aggs),
@@ -95,34 +75,24 @@ std::unique_ptr<PlanStage> HashAggStage::clone() const {
_optimizedClose,
_collatorSlot,
_allowDiskUse,
- std::move(mergingExprsClone),
- _commonStats.nodeId,
- _forceIncreasedSpilling);
+ _commonStats.nodeId);
}
void HashAggStage::doSaveState(bool relinquishCursor) {
if (relinquishCursor) {
if (_rsCursor) {
- _recordStore->saveCursor(_opCtx, _rsCursor);
+ _rsCursor->save();
}
}
if (_rsCursor) {
_rsCursor->setSaveStorageCursorOnDetachFromOperationContext(!relinquishCursor);
}
-
- if (_recordStore) {
- _recordStore->saveState();
- }
}
void HashAggStage::doRestoreState(bool relinquishCursor) {
invariant(_opCtx);
- if (_recordStore) {
- _recordStore->restoreState();
- }
-
if (_rsCursor && relinquishCursor) {
- auto couldRestore = _recordStore->restoreCursor(_opCtx, _rsCursor);
+ auto couldRestore = _rsCursor->restore();
uassert(6196500, "HashAggStage could not restore cursor", couldRestore);
}
}
@@ -150,36 +120,34 @@ void HashAggStage::prepare(CompileCtx& ctx) {
}
value::SlotSet dupCheck;
- auto throwIfDupSlot = [&dupCheck](value::SlotId slot) {
- auto [_, inserted] = dupCheck.emplace(slot);
- tassert(7039551, "duplicate slot id", inserted);
- };
-
size_t counter = 0;
// Process group by columns.
for (auto& slot : _gbs) {
- throwIfDupSlot(slot);
+ auto [it, inserted] = dupCheck.emplace(slot);
+ uassert(4822827, str::stream() << "duplicate field: " << slot, inserted);
_inKeyAccessors.emplace_back(_children[0]->getAccessor(ctx, slot));
- // Construct accessors for obtaining the key values from either the hash table '_ht' or the
- // '_recordStore'.
+ // Construct accessors for the key to be processed from either the '_ht' or the
+ // '_recordStore'. Before the memory limit is reached the '_outHashKeyAccessors' will carry
+ // the group-by keys, otherwise the '_outRecordStoreKeyAccessors' will carry the group-by
+ // keys.
_outHashKeyAccessors.emplace_back(std::make_unique<HashKeyAccessor>(_htIt, counter));
_outRecordStoreKeyAccessors.emplace_back(
- std::make_unique<value::MaterializedSingleRowAccessor>(_outKeyRowRecordStore, counter));
+ std::make_unique<value::MaterializedSingleRowAccessor>(_aggKeyRecordStore, counter));
+
+ counter++;
- // A 'SwitchAccessor' is used to point the '_outKeyAccessors' to the key coming from the
- // '_ht' or the '_recordStore' when draining the HashAgg stage in getNext(). If no spilling
- // occurred, the keys will be obtained from the hash table. If spilling kicked in, then all
- // of the data is written out to the record store, so the 'SwitchAccessor' is reconfigured
- // to obtain all of the keys from the spill table.
+ // A SwitchAccessor is used to point the '_outKeyAccessors' to the key coming from the '_ht'
+ // or the '_recordStore' when draining the HashAgg stage in getNext. The group-by key will
+ // either be in the '_ht' or the '_recordStore' if the key lives in memory, or if the key
+ // has been spilled to disk, respectively. The SwitchAccessor allows toggling between the
+ // two so the parent stage can read it through the '_outAccessors'.
_outKeyAccessors.emplace_back(
std::make_unique<value::SwitchAccessor>(std::vector<value::SlotAccessor*>{
_outHashKeyAccessors.back().get(), _outRecordStoreKeyAccessors.back().get()}));
_outAccessors[slot] = _outKeyAccessors.back().get();
-
- ++counter;
}
// Process seek keys (if any). The keys must come from outside of the subtree (by definition) so
@@ -190,18 +158,26 @@ void HashAggStage::prepare(CompileCtx& ctx) {
counter = 0;
for (auto& [slot, expr] : _aggs) {
- throwIfDupSlot(slot);
-
- // Just like with the output accessors for the keys, we construct output accessors for the
- // aggregate values that read from either the hash table '_ht' or the '_recordStore'.
+ auto [it, inserted] = dupCheck.emplace(slot);
+ // Some compilers do not allow to capture local bindings by lambda functions (the one
+ // is used implicitly in uassert below), so we need a local variable to construct an
+ // error message.
+ const auto slotId = slot;
+ uassert(4822828, str::stream() << "duplicate field: " << slotId, inserted);
+
+ // Construct accessors for the agg state to be processed from either the '_ht' or the
+ // '_recordStore' by the SwitchAccessor owned by '_outAggAccessors' below.
_outRecordStoreAggAccessors.emplace_back(
- std::make_unique<value::MaterializedSingleRowAccessor>(_outAggRowRecordStore, counter));
+ std::make_unique<value::MaterializedSingleRowAccessor>(_aggValueRecordStore, counter));
_outHashAggAccessors.emplace_back(std::make_unique<HashAggAccessor>(_htIt, counter));
-
- // A 'SwitchAccessor' is used to toggle the '_outAggAccessors' between the '_ht' and the
- // '_recordStore'. Just like the key values, the aggregate values are always obtained from
- // the hash table if no spilling occurred and are always obtained from the record store if
- // spilling occurred.
+ counter++;
+
+ // A SwitchAccessor is used to toggle the '_outAggAccessors' between the '_ht' and the
+ // '_recordStore' when updating the agg state via the bytecode. By compiling the agg
+ // EExpressions with a SwitchAccessor we can load the agg value into the memory of
+ // '_aggValueRecordStore' if the value comes from the '_recordStore' or we can use the
+ // agg value referenced through '_htIt' and run the bytecode to mutate the value through the
+ // SwitchAccessor.
_outAggAccessors.emplace_back(
std::make_unique<value::SwitchAccessor>(std::vector<value::SlotAccessor*>{
_outHashAggAccessors.back().get(), _outRecordStoreAggAccessors.back().get()}));
@@ -211,32 +187,10 @@ void HashAggStage::prepare(CompileCtx& ctx) {
ctx.root = this;
ctx.aggExpression = true;
ctx.accumulator = _outAggAccessors.back().get();
+
_aggCodes.emplace_back(expr->compile(ctx));
ctx.aggExpression = false;
-
- ++counter;
- }
-
- // If disk use is allowed, then we need to compile the merging expressions as well.
- if (_allowDiskUse) {
- counter = 0;
- for (auto&& [spillSlot, mergingExpr] : _mergingExprs) {
- throwIfDupSlot(spillSlot);
-
- _spilledAggsAccessors.push_back(
- std::make_unique<value::MaterializedSingleRowAccessor>(_spilledAggRow, counter));
- _spilledAggsAccessorMap[spillSlot] = _spilledAggsAccessors.back().get();
-
- ctx.root = this;
- ctx.aggExpression = true;
- ctx.accumulator = _outAggAccessors[counter].get();
- _mergingExprCodes.emplace_back(mergingExpr->compile(ctx));
- ctx.aggExpression = false;
-
- ++counter;
- }
}
-
_compiled = true;
}
@@ -246,15 +200,6 @@ value::SlotAccessor* HashAggStage::getAccessor(CompileCtx& ctx, value::SlotId sl
return it->second;
}
} else {
- // The slots into which we read spilled partial aggregates, accessible via
- // '_spilledAggsAccessors', should only be visible to this stage. They are used internally
- // when merging spilled partial aggregates and should never be read by ancestor stages.
- // Therefore, they are only made visible when this stage is in the process of compiling
- // itself.
- if (auto it = _spilledAggsAccessorMap.find(slot); it != _spilledAggsAccessorMap.end()) {
- return it->second;
- }
-
return _children[0]->getAccessor(ctx, slot);
}
@@ -270,117 +215,99 @@ void HashAggStage::makeTemporaryRecordStore() {
"No storage engine so HashAggStage cannot spill to disk",
_opCtx->getServiceContext()->getStorageEngine());
assertIgnorePrepareConflictsBehavior(_opCtx);
- _recordStore = std::make_unique<SpillingStore>(_opCtx);
+ _recordStore = _opCtx->getServiceContext()->getStorageEngine()->makeTemporaryRecordStore(
+ _opCtx, KeyFormat::String);
_specificStats.usedDisk = true;
}
void HashAggStage::spillRowToDisk(const value::MaterializedRow& key,
const value::MaterializedRow& val) {
- CollatorInterface* collator = nullptr;
- if (_collatorAccessor) {
- auto [colTag, colVal] = _collatorAccessor->getViewOfValue();
- collator = value::getCollatorView(colVal);
- }
-
KeyString::Builder kb{KeyString::Version::kLatestVersion};
- // Serialize the key that will be used as the record id (rid) when storing the record in the
- // record store. Use a keystring for the spilled entry's rid such that partial aggregates are
- // guaranteed to have identical keystrings when their keys are equal with respect to the
- // collation.
- key.serializeIntoKeyString(kb, collator);
- // Add a unique integer to the end of the key, since record ids must be unique. We want equal
- // keys to be adjacent in the 'RecordStore' so that we can merge the partial aggregates with a
- // single pass.
- kb.appendNumberLong(_ridCounter++);
- auto rid = RecordId(kb.getBuffer(), kb.getSize());
+ key.serializeIntoKeyString(kb);
+ auto typeBits = kb.getTypeBits();
- if (collator) {
- // The keystring cannot always be deserialized back to the original keys when a collation is
- // in use, so we also store the unmodified key in the data part of the spilled record.
- _recordStore->upsertToRecordStore(_opCtx, rid, key, val, false /*update*/);
- } else {
- auto typeBits = kb.getTypeBits();
- _recordStore->upsertToRecordStore(_opCtx, rid, val, typeBits, false /*update*/);
- }
+ auto rid = RecordId(kb.getBuffer(), kb.getSize());
+ boost::optional<value::MaterializedRow> valFromRs =
+ readFromRecordStore(_opCtx, _recordStore->rs(), rid);
+ tassert(6031100, "Spilling a row doesn't support updating it in the store.", !valFromRs);
- _specificStats.spilledRecords++;
+ spillValueToDisk(rid, val, typeBits, false /*update*/);
}
-void HashAggStage::spill(MemoryCheckData& mcd) {
- uassert(ErrorCodes::QueryExceededMemoryLimitNoDiskUseAllowed,
- "Exceeded memory limit for $group, but didn't allow external spilling;"
- " pass allowDiskUse:true to opt in",
- _allowDiskUse);
-
- // Since we flush the entire hash table to disk, we also clear any state related to estimating
- // memory consumption.
- mcd.reset();
-
- if (!_recordStore) {
- makeTemporaryRecordStore();
- }
-
- for (auto&& it : *_ht) {
- spillRowToDisk(it.first, it.second);
+void HashAggStage::spillValueToDisk(const RecordId& key,
+ const value::MaterializedRow& val,
+ const KeyString::TypeBits& typeBits,
+ bool update) {
+ auto nBytes = upsertToRecordStore(_opCtx, _recordStore->rs(), key, val, typeBits, update);
+ if (!update) {
+ _specificStats.spilledRecords++;
}
-
- auto& metricsCollector = ResourceConsumption::MetricsCollector::get(_opCtx);
- // We're not actually doing any sorting here or using the 'Sorter' class, but for the purposes
- // of $operationMetrics we incorporate the number of spilled records into the "keysSorted"
- // metric. Similarly, "sorterSpills" despite the name counts the number of individual spill
- // events.
- metricsCollector.incrementKeysSorted(_ht->size());
- metricsCollector.incrementSorterSpills(1);
-
- _ht->clear();
-
- ++_specificStats.numSpills;
+ _specificStats.lastSpilledRecordSize = nBytes;
}
// Checks memory usage. Ideally, we'd want to know the exact size of already accumulated data, but
// we cannot, so we estimate it based on the last updated/inserted row, if we have one, or the first
// row in the '_ht' table. If the estimated memory usage exceeds the allowed, this method initiates
-// spilling.
+// spilling (if haven't been done yet) and evicts some records from the '_ht' table into the temp
+// store to keep the memory usage under the limit.
void HashAggStage::checkMemoryUsageAndSpillIfNecessary(MemoryCheckData& mcd) {
- invariant(!_ht->empty());
+ // The '_ht' table might become empty in the degenerate case when all rows had to be evicted to
+ // meet the memory constraint during a previous check -- we don't need to keep checking memory
+ // usage in this case because the table will never get new rows.
+ if (_ht->empty()) {
+ return;
+ }
// If the group-by key is empty we will only ever aggregate into a single row so no sense in
- // spilling.
+ // spilling since we will just be moving a single row back and forth from disk to main memory.
if (_inKeyAccessors.size() == 0) {
return;
}
mcd.memoryCheckpointCounter++;
- if (mcd.memoryCheckpointCounter < mcd.nextMemoryCheckpoint) {
- // We haven't reached the next checkpoint at which we estimate memory usage and decide if we
- // should spill.
- return;
- }
-
- const long estimatedRowSize =
- _htIt->first.memUsageForSorter() + _htIt->second.memUsageForSorter();
- long long estimatedTotalSize = _ht->size() * estimatedRowSize;
-
- if (estimatedTotalSize >= _approxMemoryUseInBytesBeforeSpill) {
- spill(mcd);
- } else {
- // Calculate the next memory checkpoint. We estimate it based on the prior growth of the
- // '_ht' and the remaining available memory. If 'estimatedGainPerChildAdvance' suggests that
- // the hash table is growing, then the checkpoint is estimated as some configurable
- // percentage of the number of additional input rows that we would have to process to
- // consume the remaining memory. On the other hand, a value of 'estimtedGainPerChildAdvance'
- // close to zero indicates a stable hash stable size, in which case we can delay the next
- // check progressively.
+ if (mcd.memoryCheckpointCounter >= mcd.nextMemoryCheckpoint) {
+ if (_htIt == _ht->end()) {
+ _htIt = _ht->begin();
+ }
+ const long estimatedRowSize =
+ _htIt->first.memUsageForSorter() + _htIt->second.memUsageForSorter();
+ long long estimatedTotalSize = _ht->size() * estimatedRowSize;
const double estimatedGainPerChildAdvance =
(static_cast<double>(estimatedTotalSize - mcd.lastEstimatedMemoryUsage) /
mcd.memoryCheckpointCounter);
+ if (estimatedTotalSize >= _approxMemoryUseInBytesBeforeSpill) {
+ uassert(ErrorCodes::QueryExceededMemoryLimitNoDiskUseAllowed,
+ "Exceeded memory limit for $group, but didn't allow external spilling."
+ " Pass allowDiskUse:true to opt in.",
+ _allowDiskUse);
+ if (!_recordStore) {
+ makeTemporaryRecordStore();
+ }
+
+ // Evict enough rows into the temporary store to drop below the memory constraint.
+ const long rowsToEvictCount =
+ 1 + (estimatedTotalSize - _approxMemoryUseInBytesBeforeSpill) / estimatedRowSize;
+ for (long i = 0; !_ht->empty() && i < rowsToEvictCount; i++) {
+ spillRowToDisk(_htIt->first, _htIt->second);
+ _ht->erase(_htIt);
+ _htIt = _ht->begin();
+ }
+ estimatedTotalSize = _ht->size() * estimatedRowSize;
+ }
+
+ // Calculate the next memory checkpoint. We estimate it based on the prior growth of the
+ // '_ht' and the remaining available memory. We have to keep doing this even after starting
+ // to spill because some accumulators can grow in size inside '_ht' (with no bounds).
+ // Value of 'estimatedGainPerChildAdvance' can be negative if the previous checkpoint
+ // evicted any records. And a value close to zero indicates a stable size of '_ht' so can
+ // delay the next check progressively.
const long nextCheckpointCandidate = (estimatedGainPerChildAdvance > 0.1)
? mcd.checkpointMargin * (_approxMemoryUseInBytesBeforeSpill - estimatedTotalSize) /
estimatedGainPerChildAdvance
- : mcd.nextMemoryCheckpoint * 2;
-
+ : (estimatedGainPerChildAdvance < -0.1) ? mcd.atMostCheckFrequency
+ : mcd.nextMemoryCheckpoint * 2;
mcd.nextMemoryCheckpoint =
std::min<long>(mcd.memoryCheckFrequency,
std::max<long>(mcd.atMostCheckFrequency, nextCheckpointCandidate));
@@ -406,30 +333,17 @@ void HashAggStage::open(bool reOpen) {
5402503, "collatorSlot must be of collator type", tag == value::TypeTags::collator);
auto collatorView = value::getCollatorView(collatorVal);
const value::MaterializedRowHasher hasher(collatorView);
- _keyEq = value::MaterializedRowEq(collatorView);
- _ht.emplace(0, hasher, _keyEq);
+ const value::MaterializedRowEq equator(collatorView);
+ _ht.emplace(0, hasher, equator);
} else {
_ht.emplace();
}
_seekKeys.resize(_seekKeysAccessors.size());
- // Reset state since this stage may have been previously opened.
- for (auto&& accessor : _outKeyAccessors) {
- accessor->setIndex(0);
- }
- for (auto&& accessor : _outAggAccessors) {
- accessor->setIndex(0);
- }
- if (_recordStore) {
- _recordStore->resetCursor(_opCtx, _rsCursor);
- }
- _recordStore.reset();
- _outKeyRowRecordStore = {0};
- _outAggRowRecordStore = {0};
- _spilledAggRow = {0};
- _stashedNextRow = {0, 0};
-
+ // A default value for spilling a key to the record store.
+ value::MaterializedRow defaultVal{_outAggAccessors.size()};
+ bool updateAggStateHt = false;
MemoryCheckData memoryCheckData;
while (_children[0]->getNext() == PlanState::ADVANCED) {
@@ -441,35 +355,57 @@ void HashAggStage::open(bool reOpen) {
key.reset(idx++, false, tag, val);
}
- bool newKey = false;
- _htIt = _ht->find(key);
- if (_htIt == _ht->end()) {
- // The key is not present in the hash table yet, so we insert it and initialize the
- // corresponding accumulator. Note that as a future optimization, we could avoid
- // doing a lookup both in the 'find()' call and in 'emplace()'.
- newKey = true;
+
+ if (_htIt = _ht->find(key); !_recordStore && _htIt == _ht->end()) {
+ // The memory limit hasn't been reached yet, insert a new key in '_ht' by copying
+ // the key. Note as a future optimization, we should avoid the lookup in the find()
+ // call and the emplace.
key.makeOwned();
auto [it, _] = _ht->emplace(std::move(key), value::MaterializedRow{0});
+ // Initialize accumulators.
it->second.resize(_outAggAccessors.size());
_htIt = it;
}
-
- // Accumulate state in '_ht'.
- for (size_t idx = 0; idx < _outAggAccessors.size(); ++idx) {
- auto [owned, tag, val] = _bytecode.run(_aggCodes[idx].get());
- _outHashAggAccessors[idx]->reset(owned, tag, val);
- }
-
- if (_forceIncreasedSpilling && !newKey) {
- // If configured to spill more than usual, we spill after seeing the same key twice.
- spill(memoryCheckData);
+ updateAggStateHt = _htIt != _ht->end();
+
+ if (updateAggStateHt) {
+ // Accumulate state in '_ht' by pointing the '_outAggAccessors' the
+ // '_outHashAggAccessors'.
+ for (size_t idx = 0; idx < _outAggAccessors.size(); ++idx) {
+ _outAggAccessors[idx]->setIndex(0);
+ auto [owned, tag, val] = _bytecode.run(_aggCodes[idx].get());
+ _outHashAggAccessors[idx]->reset(owned, tag, val);
+ }
} else {
- // Estimates how much memory is being used. If we estimate that the hash table
- // exceeds the allotted memory budget, its contents are spilled to the
- // '_recordStore' and '_ht' is cleared.
- checkMemoryUsageAndSpillIfNecessary(memoryCheckData);
+ // The memory limit has been reached and the key wasn't in the '_ht' so we need
+ // to spill it to the '_recordStore'.
+ KeyString::Builder kb{KeyString::Version::kLatestVersion};
+ // 'key' is moved only when 'updateAggStateHt' ends up "true", so it's safe to
+ // ignore the warning.
+ key.serializeIntoKeyString(kb); // NOLINT(bugprone-use-after-move)
+ auto typeBits = kb.getTypeBits();
+
+ auto rid = RecordId(kb.getBuffer(), kb.getSize());
+
+ boost::optional<value::MaterializedRow> valFromRs =
+ readFromRecordStore(_opCtx, _recordStore->rs(), rid);
+ if (!valFromRs) {
+ _aggValueRecordStore = defaultVal;
+ } else {
+ _aggValueRecordStore = *valFromRs;
+ }
+
+ for (size_t idx = 0; idx < _outAggAccessors.size(); ++idx) {
+ _outAggAccessors[idx]->setIndex(1);
+ auto [owned, tag, val] = _bytecode.run(_aggCodes[idx].get());
+ _aggValueRecordStore.reset(idx, owned, tag, val);
+ }
+ spillValueToDisk(rid, _aggValueRecordStore, typeBits, valFromRs ? true : false);
}
+ // Estimates how much memory is being used and might start spilling.
+ checkMemoryUsageAndSpillIfNecessary(memoryCheckData);
+
if (_tracker && _tracker->trackProgress<TrialRunTracker::kNumResults>(1)) {
// During trial runs, we want to limit the amount of work done by opening a blocking
// stage, like this one. The blocking stage tracks the number of documents it has
@@ -485,34 +421,9 @@ void HashAggStage::open(bool reOpen) {
_children[0]->close();
_childOpened = false;
}
-
- // If we spilled at any point while consuming the input, then do one final spill to write
- // any leftover contents of '_ht' to the record store. That way, when recovering the input
- // from the record store and merging partial aggregates we don't have to worry about the
- // possibility of some of the data being in the hash table and some being in the record
- // store.
- if (_recordStore) {
- if (!_ht->empty()) {
- spill(memoryCheckData);
- }
-
- _specificStats.spilledDataStorageSize = _recordStore->rs()->storageSize(_opCtx);
-
- // Establish a cursor, positioned at the beginning of the record store.
- _rsCursor = _recordStore->getCursor(_opCtx);
-
- // Callers will be obtaining the results from the spill table, so set the
- // 'SwitchAccessors' so that they refer to the rows recovered from the record store
- // under the hood.
- for (auto&& accessor : _outKeyAccessors) {
- accessor->setIndex(1);
- }
- for (auto&& accessor : _outAggAccessors) {
- accessor->setIndex(1);
- }
- }
}
+
if (!_seekKeysAccessors.empty()) {
// Copy keys in order to do the lookup.
size_t idx = 0;
@@ -523,103 +434,20 @@ void HashAggStage::open(bool reOpen) {
}
_htIt = _ht->end();
-}
-
-HashAggStage::SpilledRow HashAggStage::deserializeSpilledRecord(const Record& record,
- BufBuilder& keyBuffer) {
- // Read the values and type bits out of the value part of the record.
- BufReader valReader(record.data.data(), record.data.size());
-
- auto val = value::MaterializedRow::deserializeForSorter(valReader, {nullptr /*collator*/});
- auto typeBits = KeyString::TypeBits::fromBuffer(KeyString::Version::kLatestVersion, &valReader);
-
- keyBuffer.reset();
- auto key = value::MaterializedRow::deserializeFromKeyString(
- decodeKeyString(record.id, typeBits), &keyBuffer, _gbs.size() /*numPrefixValuesToRead*/);
- return {std::move(key), std::move(val)};
-}
-
-HashAggStage::SpilledRow HashAggStage::deserializeSpilledRecord(const Record& record,
- const CollatorInterface& collator) {
- BufReader valReader(record.data.data(), record.data.size());
- // When a collator has been defined, both the key and the value are stored in the data part of
- // the record. First read the key and then read the value.
- auto key = value::MaterializedRow::deserializeForSorter(valReader, {&collator});
- auto val = value::MaterializedRow::deserializeForSorter(valReader, {&collator});
- return {std::move(key), std::move(val)};
-}
-
-PlanState HashAggStage::getNextSpilled() {
- CollatorInterface* collator = nullptr;
- if (_collatorAccessor) {
- auto [colTag, colVal] = _collatorAccessor->getViewOfValue();
- collator = value::getCollatorView(colVal);
- }
-
- // Use the appropriate method to deserialize the record based on whether a collator is used.
- auto recoverSpilledRecord =
- [this](const Record& record, BufBuilder& keyBuffer, const CollatorInterface* collator) {
- if (collator) {
- return deserializeSpilledRecord(record, *collator);
- }
- return deserializeSpilledRecord(record, keyBuffer);
- };
-
- if (_stashedNextRow.first.isEmpty()) {
- auto nextRecord = _rsCursor->next();
- if (!nextRecord) {
- return trackPlanState(PlanState::IS_EOF);
- }
-
- // We are just starting the process of merging the spilled file segments.
- auto recoveredRow = recoverSpilledRecord(*nextRecord, _outKeyRowRSBuffer, collator);
-
- _outKeyRowRecordStore = std::move(recoveredRow.first);
- _outAggRowRecordStore = std::move(recoveredRow.second);
- } else {
- // We peeked at the next key last time around.
- _outKeyRowRSBuffer = std::move(_stashedKeyBuffer);
- _outKeyRowRecordStore = std::move(_stashedNextRow.first);
- _outAggRowRecordStore = std::move(_stashedNextRow.second);
- // Clear the stashed row.
- _stashedNextRow = {0, 0};
- }
-
- // Find additional partial aggregates for the same key and merge them in order to compute the
- // final output.
- for (auto nextRecord = _rsCursor->next(); nextRecord; nextRecord = _rsCursor->next()) {
- auto recoveredRow = recoverSpilledRecord(*nextRecord, _stashedKeyBuffer, collator);
- if (!_keyEq(recoveredRow.first, _outKeyRowRecordStore)) {
- // The newly recovered spilled row belongs to a new key, so we're done merging partial
- // aggregates for the old key. Save the new row for later and return advanced.
- _stashedNextRow = std::move(recoveredRow);
- return trackPlanState(PlanState::ADVANCED);
- }
-
- // Merge in the new partial aggregate values.
- _spilledAggRow = std::move(recoveredRow.second);
- for (size_t idx = 0; idx < _mergingExprCodes.size(); ++idx) {
- auto [owned, tag, val] = _bytecode.run(_mergingExprCodes[idx].get());
- _outRecordStoreAggAccessors[idx]->reset(owned, tag, val);
- }
+ // Set the SwitchAccessors to point to the '_ht' so we can drain it first before draining the
+ // '_recordStore' in getNext().
+ for (size_t idx = 0; idx < _outAggAccessors.size(); ++idx) {
+ _outAggAccessors[idx]->setIndex(0);
}
-
- return trackPlanState(PlanState::ADVANCED);
+ _drainingRecordStore = false;
}
PlanState HashAggStage::getNext() {
auto optTimer(getOptTimer(_opCtx));
- // If we've spilled, then we need to produce the output by merging the spilled segments from the
- // spill file.
- if (_recordStore) {
- return getNextSpilled();
- }
-
- // We didn't spill. Obtain the next output row from the hash table.
- if (_htIt == _ht->end()) {
- // First invocation of getNext() after open().
+ if (_htIt == _ht->end() && !_drainingRecordStore) {
+ // First invocation of getNext() after open() when not draining the '_recordStore'.
if (!_seekKeysAccessors.empty()) {
_htIt = _ht->find(_seekKeys);
} else {
@@ -628,13 +456,53 @@ PlanState HashAggStage::getNext() {
} else if (!_seekKeysAccessors.empty()) {
// Subsequent invocation with seek keys. Return only 1 single row (if any).
_htIt = _ht->end();
- } else {
+ } else if (!_drainingRecordStore) {
+ // Returning the results of the entire hash table first before draining the '_recordStore'.
++_htIt;
}
- if (_htIt == _ht->end()) {
- // The hash table has been drained (and we never spilled to disk) so we're done.
+ if (_htIt == _ht->end() && !_recordStore) {
+ // The hash table has been drained and nothing was spilled to disk.
return trackPlanState(PlanState::IS_EOF);
+ } else if (_htIt != _ht->end()) {
+ // Drain the '_ht' on the next 'getNext()' call.
+ return trackPlanState(PlanState::ADVANCED);
+ } else if (_seekKeysAccessors.empty()) {
+ // A record store was created to spill to disk. Drain it then clean it up.
+ if (!_rsCursor) {
+ _rsCursor = _recordStore->rs()->getCursor(_opCtx);
+ }
+ auto nextRecord = _rsCursor->next();
+ if (nextRecord) {
+ // Point the out accessors to the recordStore accessors to allow parent stages to read
+ // the agg state from the '_recordStore'.
+ if (!_drainingRecordStore) {
+ for (size_t i = 0; i < _outKeyAccessors.size(); ++i) {
+ _outKeyAccessors[i]->setIndex(1);
+ }
+ for (size_t i = 0; i < _outAggAccessors.size(); ++i) {
+ _outAggAccessors[i]->setIndex(1);
+ }
+ }
+ _drainingRecordStore = true;
+
+ // Read the agg state value from the '_recordStore' and Reconstruct the key from the
+ // typeBits stored along side of the value.
+ BufReader valReader(nextRecord->data.data(), nextRecord->data.size());
+ auto val = value::MaterializedRow::deserializeForSorter(valReader, {});
+ auto typeBits =
+ KeyString::TypeBits::fromBuffer(KeyString::Version::kLatestVersion, &valReader);
+ _aggValueRecordStore = val;
+
+ _aggKeyRSBuffer.reset();
+ _aggKeyRecordStore = value::MaterializedRow::deserializeFromKeyString(
+ decodeKeyString(nextRecord->id, typeBits), &_aggKeyRSBuffer);
+ return trackPlanState(PlanState::ADVANCED);
+ } else {
+ _rsCursor.reset();
+ _recordStore.reset();
+ return trackPlanState(PlanState::IS_EOF);
+ }
} else {
return trackPlanState(PlanState::ADVANCED);
}
@@ -654,19 +522,11 @@ std::unique_ptr<PlanStageStats> HashAggStage::getStats(bool includeDebugInfo) co
childrenBob.append(str::stream() << slot, printer.print(expr->debugPrint()));
}
}
-
- if (!_mergingExprs.empty()) {
- BSONObjBuilder nestedBuilder{bob.subobjStart("mergingExprs")};
- for (auto&& [slot, expr] : _mergingExprs) {
- nestedBuilder.append(str::stream() << slot, printer.print(expr->debugPrint()));
- }
- }
-
// Spilling stats.
bob.appendBool("usedDisk", _specificStats.usedDisk);
- bob.appendNumber("numSpills", _specificStats.numSpills);
bob.appendNumber("spilledRecords", _specificStats.spilledRecords);
- bob.appendNumber("spilledDataStorageSize", _specificStats.spilledDataStorageSize);
+ bob.appendNumber("spilledBytesApprox",
+ _specificStats.lastSpilledRecordSize * _specificStats.spilledRecords);
ret->debugInfo = bob.obj();
}
@@ -684,15 +544,11 @@ void HashAggStage::close() {
trackClose();
_ht = boost::none;
- if (_recordStore && _opCtx) {
- _recordStore->resetCursor(_opCtx, _rsCursor);
+ if (_recordStore) {
+ // A record store was created to spill to disk. Clean it up.
+ _recordStore.reset();
+ _drainingRecordStore = false;
}
- _rsCursor.reset();
- _recordStore.reset();
- _outKeyRowRecordStore = {0};
- _outAggRowRecordStore = {0};
- _spilledAggRow = {0};
- _stashedNextRow = {0, 0};
if (_childOpened) {
_children[0]->close();
@@ -715,7 +571,7 @@ std::vector<DebugPrinter::Block> HashAggStage::debugPrint() const {
ret.emplace_back(DebugPrinter::Block("[`"));
bool first = true;
- for (auto&& [slot, expr] : _aggs) {
+ value::orderedSlotMapTraverse(_aggs, [&](auto slot, auto&& expr) {
if (!first) {
ret.emplace_back(DebugPrinter::Block("`,"));
}
@@ -724,7 +580,7 @@ std::vector<DebugPrinter::Block> HashAggStage::debugPrint() const {
ret.emplace_back("=");
DebugPrinter::addBlocks(ret, expr->debugPrint());
first = false;
- }
+ });
ret.emplace_back("`]");
if (!_seekKeysSlots.empty()) {
@@ -739,28 +595,6 @@ std::vector<DebugPrinter::Block> HashAggStage::debugPrint() const {
ret.emplace_back("`]");
}
- if (!_mergingExprs.empty()) {
- ret.emplace_back("spillSlots[`");
- for (size_t idx = 0; idx < _mergingExprs.size(); ++idx) {
- if (idx) {
- ret.emplace_back("`,");
- }
-
- DebugPrinter::addIdentifier(ret, _mergingExprs[idx].first);
- }
- ret.emplace_back("`]");
-
- ret.emplace_back("mergingExprs[`");
- for (size_t idx = 0; idx < _mergingExprs.size(); ++idx) {
- if (idx) {
- ret.emplace_back("`,");
- }
-
- DebugPrinter::addBlocks(ret, _mergingExprs[idx].second->debugPrint());
- }
- ret.emplace_back("`]");
- }
-
if (!_optimizedClose) {
ret.emplace_back("reopen");
}
@@ -781,7 +615,6 @@ size_t HashAggStage::estimateCompileTimeSize() const {
size += size_estimator::estimate(_gbs);
size += size_estimator::estimate(_aggs);
size += size_estimator::estimate(_seekKeysSlots);
- size += size_estimator::estimate(_mergingExprs);
return size;
}
diff --git a/src/mongo/db/exec/sbe/stages/hash_agg.h b/src/mongo/db/exec/sbe/stages/hash_agg.h
index 91f91051363..19fbca9d1c7 100644
--- a/src/mongo/db/exec/sbe/stages/hash_agg.h
+++ b/src/mongo/db/exec/sbe/stages/hash_agg.h
@@ -29,9 +29,10 @@
#pragma once
+#include <unordered_map>
+
#include "mongo/db/exec/sbe/expressions/expression.h"
#include "mongo/db/exec/sbe/stages/stages.h"
-#include "mongo/db/exec/sbe/util/spilling.h"
#include "mongo/db/exec/sbe/vm/vm.h"
#include "mongo/db/query/query_knobs_gen.h"
#include "mongo/db/storage/temporary_record_store.h"
@@ -60,38 +61,21 @@ namespace sbe {
* determining whether two group-by keys are equal. For instance, the plan may require us to do a
* case-insensitive group on a string field.
*
- * The 'allowDiskUse' flag controls whether this stage can spill. If false and the memory budget is
- * exhausted, this stage throws a query-fatal error with code
- * 'QueryExceededMemoryLimitNoDiskUseAllowed'. If true, then spilling is possible and the caller
- * must provide a vector of 'mergingExprs'. This is a vector of (slot, expression) pairs which is
- * symmetrical with 'aggs'. The slots are only visible internally and are used to store partial
- * aggregate values that have been recovered from the spill table. Each of the expressions is an agg
- * function which merges the partial aggregate value from this slot into the final aggregate value.
- * In the debug string output, the internal slots used to house the partial aggregates are printed
- * as a list of "spillSlots" and the expressions are printed as a parallel list of "mergingExprs".
- *
- * If 'forcedIncreasedSpilling' is true, then this stage will spill frequently even if the memory
- * limit is not reached. This is intended to be used in test contexts to exercise the otherwise
- * infrequently used spilling logic.
- *
* Debug string representation:
*
- * group [<group by slots>] [slot_1 = expr_1, ..., slot_n = expr_n] [<seek slots>]?
- * spillSlots[slot_1, ..., slot_n] mergingExprs[expr_1, ..., expr_n] reopen? collatorSlot?
- * childStage
+ * group [<group by slots>] [slot_1 = expr_1, ..., slot_n = expr_n] [<seek slots>]? reopen?
+ * collatorSlot? childStage
*/
class HashAggStage final : public PlanStage {
public:
HashAggStage(std::unique_ptr<PlanStage> input,
value::SlotVector gbs,
- SlotExprPairVector aggs,
+ value::SlotMap<std::unique_ptr<EExpression>> aggs,
value::SlotVector seekKeysSlots,
bool optimizedClose,
boost::optional<value::SlotId> collatorSlot,
bool allowDiskUse,
- SlotExprPairVector mergingExprs,
- PlanNodeId planNodeId,
- bool forceIncreasedSpilling = false);
+ PlanNodeId planNodeId);
std::unique_ptr<PlanStage> clone() const final;
@@ -124,147 +108,99 @@ private:
using HashKeyAccessor = value::MaterializedRowKeyAccessor<TableType::iterator>;
using HashAggAccessor = value::MaterializedRowValueAccessor<TableType::iterator>;
- using SpilledRow = std::pair<value::MaterializedRow, value::MaterializedRow>;
+ void makeTemporaryRecordStore();
+
+ /**
+ * Spills a key and value pair to the '_recordStore' where the semantics are insert or update
+ * depending on the 'update' flag. When the 'update' flag is true this method already expects
+ * the 'key' to be inserted into the '_recordStore', otherwise the 'key' and 'val' pair are
+ * fresh.
+ *
+ * This method expects the key to be seralized into a KeyString::Value so that the key is
+ * memcmp-able and lookups can be done to update the 'val' in the '_recordStore'. Note that the
+ * 'typeBits' are needed to reconstruct the spilled 'key' when calling 'getNext' to deserialize
+ * the 'key' to a MaterializedRow. Since the '_recordStore' only stores the memcmp-able part of
+ * the KeyString we need to carry the 'typeBits' separately, and we do this by appending the
+ * 'typeBits' to the end of the serialized 'val' buffer and store them at the leaves of the
+ * backing B-tree of the '_recordStore'. used as the RecordId.
+ */
+ void spillValueToDisk(const RecordId& key,
+ const value::MaterializedRow& val,
+ const KeyString::TypeBits& typeBits,
+ bool update);
+ void spillRowToDisk(const value::MaterializedRow& key,
+ const value::MaterializedRow& defaultVal);
/**
* We check amount of used memory every T processed incoming records, where T is calculated
* based on the estimated used memory and its recent growth. When the memory limit is exceeded,
- * 'checkMemoryUsageAndSpillIfNecessary()' will create '_recordStore' (if it hasn't already been
- * created) and spill the contents of the hash table into this record store.
+ * 'checkMemoryUsageAndSpillIfNecessary()' will create '_recordStore' and might spill some of
+ * the already accumulated data into it.
*/
struct MemoryCheckData {
- MemoryCheckData() {
- reset();
- }
-
- void reset() {
- memoryCheckFrequency = std::min(atMostCheckFrequency, atLeastMemoryCheckFrequency);
- nextMemoryCheckpoint = 0;
- memoryCheckpointCounter = 0;
- lastEstimatedMemoryUsage = 0;
- }
-
const double checkpointMargin = internalQuerySBEAggMemoryUseCheckMargin.load();
- const int64_t atMostCheckFrequency = internalQuerySBEAggMemoryCheckPerAdvanceAtMost.load();
- const int64_t atLeastMemoryCheckFrequency =
+ const long atMostCheckFrequency = internalQuerySBEAggMemoryCheckPerAdvanceAtMost.load();
+ const long atLeastMemoryCheckFrequency =
internalQuerySBEAggMemoryCheckPerAdvanceAtLeast.load();
// The check frequency upper bound, which start at 'atMost' and exponentially backs off
// to 'atLeast' as more data is accumulated. If 'atLeast' is less than 'atMost', the memory
// checks will be done every 'atLeast' incoming records.
- int64_t memoryCheckFrequency = 1;
+ long memoryCheckFrequency = 1;
// The number of incoming records to process before the next memory checkpoint.
- int64_t nextMemoryCheckpoint = 0;
+ long nextMemoryCheckpoint = 0;
// The counter of the incoming records between memory checkpoints.
- int64_t memoryCheckpointCounter = 0;
-
- int64_t lastEstimatedMemoryUsage = 0;
- };
+ long memoryCheckpointCounter = 0;
- /**
- * Inserts a key and value pair to the '_recordStore'. They key is serialized to a
- * 'KeyString::Value' which becomes the 'RecordId'. This makes the keys memcmp-able and ensures
- * that the record store ends up sorted by the group-by keys.
- *
- * Note that the 'typeBits' are needed to reconstruct the spilled 'key' to a 'MaterializedRow',
- * but are not necessary for comparison purposes. Therefore, we carry the type bits separately
- * from the record id, instead appending them to the end of the serialized 'val' buffer.
- */
- void spillRowToDisk(const value::MaterializedRow& key, const value::MaterializedRow& val);
+ long long lastEstimatedMemoryUsage = 0;
+ MemoryCheckData() {
+ memoryCheckFrequency = std::min(atMostCheckFrequency, atLeastMemoryCheckFrequency);
+ }
+ };
void checkMemoryUsageAndSpillIfNecessary(MemoryCheckData& mcd);
- void spill(MemoryCheckData& mcd);
-
- /**
- * Given a 'record' from the record store, decodes it into a pair of materialized rows (one for
- * the group-by keys and another for the agg values).
- *
- * The given 'keyBuffer' is cleared, and then used to hold data (e.g. long strings and other
- * values that can't be inlined) obtained by decoding the 'RecordId' keystring to a
- * 'MaterializedRow'. The values in the resulting 'MaterializedRow' may be pointers into
- * 'keyBuffer', so it is important that 'keyBuffer' outlive the row.
- *
- * This method is used when there is no collator.
- */
- SpilledRow deserializeSpilledRecord(const Record& record, BufBuilder& keyBuffer);
-
- /**
- * Given a 'record' from the record store and a 'collator', decodes it into a pair of
- * materialized rows (one for the group-by key and another one for the agg value).
- * Both the group-by key and the agg value are read from the data part of the record.
- */
- SpilledRow deserializeSpilledRecord(const Record& record, const CollatorInterface& collator);
-
- PlanState getNextSpilled();
-
- void makeTemporaryRecordStore();
const value::SlotVector _gbs;
- const SlotExprPairVector _aggs;
+ const value::SlotMap<std::unique_ptr<EExpression>> _aggs;
const boost::optional<value::SlotId> _collatorSlot;
const bool _allowDiskUse;
const value::SlotVector _seekKeysSlots;
// When this operator does not expect to be reopened (almost always) then it can close the child
// early.
const bool _optimizedClose{true};
-
- // Expressions used to merge partial aggregates that have been spilled to disk and their
- // corresponding input slots. For example, imagine that this list contains a pair (s12,
- // sum(s12)). This means that the partial aggregate values will be read into slot s12 after
- // being recovered from the spill table and can be merged using the 'sum()' agg function.
- //
- // When disk use is allowed, this vector must have the same length as '_aggs'.
- const SlotExprPairVector _mergingExprs;
-
- // When true, we spill frequently without reaching the memory limit. This allows us to exercise
- // the spilling logic more often in test contexts.
- const bool _forceIncreasedSpilling;
-
value::SlotAccessorMap _outAccessors;
-
- // Accessors used to obtain the values of the group by slots when reading the input from the
- // child.
std::vector<value::SlotAccessor*> _inKeyAccessors;
- // This buffer stores values for '_outKeyRowRecordStore'; values in the '_outKeyRowRecordStore'
- // can be pointers that point to data in this buffer.
- BufBuilder _outKeyRowRSBuffer;
- // Accessors for the key slots provided as output by this stage. The keys can either come from
- // the hash table or recovered from a temporary record store. We use a 'SwitchAccessor' to
- // switch between these two cases.
+ // Accessors for the key stored in '_ht', a SwitchAccessor is used so we can produce the key
+ // from either the '_ht' or the '_recordStore'.
std::vector<std::unique_ptr<HashKeyAccessor>> _outHashKeyAccessors;
- // Row of key values to output used when recovering spilled data from the record store.
- value::MaterializedRow _outKeyRowRecordStore{0};
- std::vector<std::unique_ptr<value::MaterializedSingleRowAccessor>> _outRecordStoreKeyAccessors;
std::vector<std::unique_ptr<value::SwitchAccessor>> _outKeyAccessors;
- // Accessors for the output aggregate results. The aggregates can either come from the hash
- // table or can be computed after merging partial aggregates spilled to a record store. We use a
- // 'SwitchAccessor' to switch between these two cases.
- std::vector<std::unique_ptr<HashAggAccessor>> _outHashAggAccessors;
- // Row of agg values to output used when recovering spilled data from the record store.
- value::MaterializedRow _outAggRowRecordStore{0};
+ // Accessor for the agg state value stored in the '_recordStore' when data is spilled to disk.
+ value::MaterializedRow _aggKeyRecordStore{0};
+ value::MaterializedRow _aggValueRecordStore{0};
+ std::vector<std::unique_ptr<value::MaterializedSingleRowAccessor>> _outRecordStoreKeyAccessors;
std::vector<std::unique_ptr<value::MaterializedSingleRowAccessor>> _outRecordStoreAggAccessors;
- std::vector<std::unique_ptr<value::SwitchAccessor>> _outAggAccessors;
+
+ // This buffer stores values for the spilled '_aggKeyRecordStore' that's loaded into memory from
+ // the '_recordStore'. Values in the '_aggKeyRecordStore' row are pointers that point to data in
+ // this buffer.
+ BufBuilder _aggKeyRSBuffer;
std::vector<value::SlotAccessor*> _seekKeysAccessors;
value::MaterializedRow _seekKeys;
- // Bytecode which gets executed to aggregate incoming rows into the hash table.
+ // Accesors for the agg state in '_ht', a SwitchAccessor is used so we can produce the agg state
+ // from either the '_ht' or the '_recordStore' when draining the HashAgg stage.
+ std::vector<std::unique_ptr<value::SwitchAccessor>> _outAggAccessors;
+ std::vector<std::unique_ptr<HashAggAccessor>> _outHashAggAccessors;
std::vector<std::unique_ptr<vm::CodeFragment>> _aggCodes;
- // Bytecode for the merging expressions, executed if partial aggregates are spilled to a record
- // store and need to be subsequently combined.
- std::vector<std::unique_ptr<vm::CodeFragment>> _mergingExprCodes;
// Only set if collator slot provided on construction.
value::SlotAccessor* _collatorAccessor = nullptr;
- // Function object which can be used to check whether two materialized rows of key values are
- // equal. This comparison is collation-aware if the query has a non-simple collation.
- value::MaterializedRowEq _keyEq;
-
boost::optional<TableType> _ht;
TableType::iterator _htIt;
@@ -276,31 +212,10 @@ private:
// Memory tracking and spilling to disk.
const long long _approxMemoryUseInBytesBeforeSpill =
internalQuerySBEAggApproxMemoryUseInBytesBeforeSpill.load();
-
- // A record store which is instantiated and written to in the case of spilling.
- std::unique_ptr<SpillingStore> _recordStore;
+ std::unique_ptr<TemporaryRecordStore> _recordStore;
+ bool _drainingRecordStore{false};
std::unique_ptr<SeekableRecordCursor> _rsCursor;
- // A monotically increasing counter used to ensure uniqueness of 'RecordId' values. When
- // spilling, the key is encoding into the 'RecordId' of the '_recordStore'. Record ids must be
- // unique by definition, but we might end up spilling multiple partial aggregates for the same
- // key. We ensure uniqueness by appending a unique integer to the end of this key, which is
- // simply ignored during deserialization.
- int64_t _ridCounter = 0;
-
- // Partial aggregates that have been spilled are read into '_spilledAggRow' and read using
- // '_spilledAggsAccessors' so that they can be merged to compute the final aggregate value.
- value::MaterializedRow _spilledAggRow{0};
- std::vector<std::unique_ptr<value::MaterializedSingleRowAccessor>> _spilledAggsAccessors;
- value::SlotAccessorMap _spilledAggsAccessorMap;
-
- // Buffer to hold data for the deserialized key values from '_stashedNextRow'.
- BufBuilder _stashedKeyBuffer;
- // Place to stash the next keys and values during the streaming phase. The record store cursor
- // doesn't offer a "peek" API, so we need to hold onto the next row between getNext() calls when
- // the key value advances.
- SpilledRow _stashedNextRow;
-
HashAggStats _specificStats;
// If provided, used during a trial run to accumulate certain execution stats. Once the trial
diff --git a/src/mongo/db/exec/sbe/stages/hash_lookup.cpp b/src/mongo/db/exec/sbe/stages/hash_lookup.cpp
index 02e95307c4b..0d976f1a875 100644
--- a/src/mongo/db/exec/sbe/stages/hash_lookup.cpp
+++ b/src/mongo/db/exec/sbe/stages/hash_lookup.cpp
@@ -187,22 +187,6 @@ value::SlotAccessor* HashLookupStage::getAccessor(CompileCtx& ctx, value::SlotId
return outerChild()->getAccessor(ctx, slot);
}
}
-void HashLookupStage::doSaveState(bool relinquishCursor) {
- if (_recordStoreHt) {
- _recordStoreHt->saveState();
- }
- if (_recordStoreBuf) {
- _recordStoreBuf->saveState();
- }
-}
-void HashLookupStage::doRestoreState(bool relinquishCursor) {
- if (_recordStoreHt) {
- _recordStoreHt->restoreState();
- }
- if (_recordStoreBuf) {
- _recordStoreBuf->restoreState();
- }
-}
void HashLookupStage::reset() {
_ht = boost::none;
@@ -275,7 +259,7 @@ void HashLookupStage::addHashTableEntry(value::SlotAccessor* keyAccessor, size_t
auto val = std::vector<size_t>{valueIndex};
auto [tagKey, valKey] = keyAccessor->getViewOfValue();
- spillIndicesToRecordStore(_recordStoreHt.get(), tagKey, valKey, val);
+ spillIndicesToRecordStore(_recordStoreHt->rs(), tagKey, valKey, val);
}
} else {
// The key is already present in '_ht' so the memory will only grow by one size_t. If we
@@ -297,7 +281,7 @@ void HashLookupStage::addHashTableEntry(value::SlotAccessor* keyAccessor, size_t
// Evict the hash table value.
_computedTotalMemUsage -= htIt->second.size() * sizeof(size_t);
htIt->second.push_back(valueIndex);
- spillIndicesToRecordStore(_recordStoreHt.get(), tagKeyView, valKeyView, htIt->second);
+ spillIndicesToRecordStore(_recordStoreHt->rs(), tagKeyView, valKeyView, htIt->second);
_ht->erase(htIt);
}
}
@@ -313,15 +297,17 @@ void HashLookupStage::makeTemporaryRecordStore() {
_opCtx->getServiceContext()->getStorageEngine());
assertIgnorePrepareConflictsBehavior(_opCtx);
- _recordStoreBuf = std::make_unique<SpillingStore>(_opCtx, KeyFormat::Long);
+ _recordStoreBuf = _opCtx->getServiceContext()->getStorageEngine()->makeTemporaryRecordStore(
+ _opCtx, KeyFormat::Long);
- _recordStoreHt = std::make_unique<SpillingStore>(_opCtx, KeyFormat::String);
+ _recordStoreHt = _opCtx->getServiceContext()->getStorageEngine()->makeTemporaryRecordStore(
+ _opCtx, KeyFormat::String);
_specificStats.usedDisk = true;
}
void HashLookupStage::spillBufferedValueToDisk(OperationContext* opCtx,
- SpillingStore* rs,
+ RecordStore* rs,
size_t bufferIdx,
const value::MaterializedRow& val) {
auto rid = getValueRecordId(bufferIdx);
@@ -329,7 +315,15 @@ void HashLookupStage::spillBufferedValueToDisk(OperationContext* opCtx,
BufBuilder buf;
val.serializeForSorter(buf);
- rs->upsertToRecordStore(opCtx, rid, buf, false);
+ assertIgnorePrepareConflictsBehavior(opCtx);
+ WriteUnitOfWork wuow(opCtx);
+
+ auto status = rs->insertRecord(opCtx, rid, buf.buf(), buf.len(), Timestamp{});
+ wuow.commit();
+
+ tassert(6373906,
+ str::stream() << "Failed to write to disk because " << status.getStatus().reason(),
+ status.isOK());
_specificStats.spilledBuffRecords++;
// Add size of record ID + size of buffer.
@@ -340,14 +334,14 @@ void HashLookupStage::spillBufferedValueToDisk(OperationContext* opCtx,
size_t HashLookupStage::bufferValueOrSpill(value::MaterializedRow& value) {
size_t bufferIndex = _valueId;
const long long newMemUsage = _computedTotalMemUsage + size_estimator::estimate(value);
- if (!hasSpilledBufToDisk() && newMemUsage <= _memoryUseInBytesBeforeSpill) {
+ if (newMemUsage <= _memoryUseInBytesBeforeSpill) {
_buffer.emplace_back(std::move(value));
_computedTotalMemUsage = newMemUsage;
} else {
if (!hasSpilledBufToDisk()) {
makeTemporaryRecordStore();
}
- spillBufferedValueToDisk(_opCtx, _recordStoreBuf.get(), bufferIndex, value);
+ spillBufferedValueToDisk(_opCtx, _recordStoreBuf->rs(), bufferIndex, value);
}
_valueId++;
return bufferIndex;
@@ -433,7 +427,7 @@ void HashLookupStage::accumulateFromValueIndices(const C& bufferIndices) {
// We must shift the '_bufferIt' index by one when using it as a RecordId because a
// RecordId of 0 is invalid.
auto rid = getValueRecordId(_bufferIt);
- auto rsValue = _recordStoreBuf->readFromRecordStore(_opCtx, rid);
+ auto rsValue = readFromRecordStore(_opCtx, _recordStoreBuf->rs(), rid);
if (!rsValue) {
tasserted(6373900, "bufferIdx not found in record store");
}
@@ -449,7 +443,7 @@ void HashLookupStage::accumulateFromValueIndices(const C& bufferIndices) {
}
}
-void HashLookupStage::writeIndicesToRecordStore(SpillingStore* rs,
+void HashLookupStage::writeIndicesToRecordStore(RecordStore* rs,
value::TypeTags tagKey,
value::Value valKey,
const std::vector<size_t>& value,
@@ -464,7 +458,7 @@ void HashLookupStage::writeIndicesToRecordStore(SpillingStore* rs,
key.reset(0, false, tagKey, valKey);
auto [rid, typeBits] = serializeKeyForRecordStore(key);
- rs->upsertToRecordStore(_opCtx, rid, buf, typeBits, update);
+ upsertToRecordStore(_opCtx, rs, rid, buf, typeBits, update);
if (!update) {
_specificStats.spilledHtRecords++;
// Add the size of key (which comprises of the memory usage for the key + its type bits),
@@ -477,7 +471,7 @@ void HashLookupStage::writeIndicesToRecordStore(SpillingStore* rs,
}
boost::optional<std::vector<size_t>> HashLookupStage::readIndicesFromRecordStore(
- SpillingStore* rs, value::TypeTags tagKey, value::Value valKey) {
+ RecordStore* rs, value::TypeTags tagKey, value::Value valKey) {
_probeKey.reset(0, false, tagKey, valKey);
auto [rid, _] = serializeKeyForRecordStore(_probeKey);
@@ -496,7 +490,7 @@ boost::optional<std::vector<size_t>> HashLookupStage::readIndicesFromRecordStore
return boost::none;
}
-void HashLookupStage::spillIndicesToRecordStore(SpillingStore* rs,
+void HashLookupStage::spillIndicesToRecordStore(RecordStore* rs,
value::TypeTags tagKey,
value::Value valKey,
const std::vector<size_t>& value) {
@@ -551,7 +545,7 @@ PlanState HashLookupStage::getNext() {
normalizeStringIfCollator(tagElemView, valElemView);
auto indicesFromRS = readIndicesFromRecordStore(
- _recordStoreHt.get(), tagElemCollView, valElemCollView);
+ _recordStoreHt->rs(), tagElemCollView, valElemCollView);
if (indicesFromRS) {
indices.insert(indicesFromRS->begin(), indicesFromRS->end());
}
@@ -573,7 +567,7 @@ PlanState HashLookupStage::getNext() {
normalizeStringIfCollator(tagKeyView, valKeyView);
auto indicesFromRS = readIndicesFromRecordStore(
- _recordStoreHt.get(), tagKeyCollView, valKeyCollView);
+ _recordStoreHt->rs(), tagKeyCollView, valKeyCollView);
if (indicesFromRS) {
accumulateFromValueIndices(*indicesFromRS);
}
diff --git a/src/mongo/db/exec/sbe/stages/hash_lookup.h b/src/mongo/db/exec/sbe/stages/hash_lookup.h
index b312e0a68f4..2e3f0b34816 100644
--- a/src/mongo/db/exec/sbe/stages/hash_lookup.h
+++ b/src/mongo/db/exec/sbe/stages/hash_lookup.h
@@ -33,7 +33,6 @@
#include "mongo/db/exec/sbe/expressions/expression.h"
#include "mongo/db/exec/sbe/stages/stages.h"
-#include "mongo/db/exec/sbe/util/spilling.h"
#include "mongo/db/exec/sbe/vm/vm.h"
#include "mongo/db/query/query_knobs_gen.h"
@@ -102,10 +101,6 @@ public:
std::vector<DebugPrinter::Block> debugPrint() const final;
size_t estimateCompileTimeSize() const final;
-protected:
- void doSaveState(bool relinquishCursor) override;
- void doRestoreState(bool relinquishCursor) override;
-
private:
using HashTableType = std::unordered_map<value::MaterializedRow, // NOLINT
std::vector<size_t>,
@@ -124,23 +119,23 @@ private:
// Spilling helpers.
void addHashTableEntry(value::SlotAccessor* keyAccessor, size_t valueIndex);
void spillBufferedValueToDisk(OperationContext* opCtx,
- SpillingStore* rs,
+ RecordStore* rs,
size_t bufferIdx,
const value::MaterializedRow&);
size_t bufferValueOrSpill(value::MaterializedRow& value);
void setInnerProjectSwitchAccessor(int idx);
- boost::optional<std::vector<size_t>> readIndicesFromRecordStore(SpillingStore* rs,
+ boost::optional<std::vector<size_t>> readIndicesFromRecordStore(RecordStore* rs,
value::TypeTags tagKey,
value::Value valKey);
- void writeIndicesToRecordStore(SpillingStore* rs,
+ void writeIndicesToRecordStore(RecordStore* rs,
value::TypeTags tagKey,
value::Value valKey,
const std::vector<size_t>& value,
bool update);
- void spillIndicesToRecordStore(SpillingStore* rs,
+ void spillIndicesToRecordStore(RecordStore* rs,
value::TypeTags tagKey,
value::Value valKey,
const std::vector<size_t>& value);
@@ -234,8 +229,8 @@ private:
// rows in '_buffer'.
long long _computedTotalMemUsage = 0;
- std::unique_ptr<SpillingStore> _recordStoreHt;
- std::unique_ptr<SpillingStore> _recordStoreBuf;
+ std::unique_ptr<TemporaryRecordStore> _recordStoreHt;
+ std::unique_ptr<TemporaryRecordStore> _recordStoreBuf;
HashLookupStats _specificStats;
};
diff --git a/src/mongo/db/exec/sbe/stages/ix_scan.cpp b/src/mongo/db/exec/sbe/stages/ix_scan.cpp
index fe88d9c9095..bfad6d9a2ae 100644
--- a/src/mongo/db/exec/sbe/stages/ix_scan.cpp
+++ b/src/mongo/db/exec/sbe/stages/ix_scan.cpp
@@ -45,7 +45,6 @@ IndexScanStage::IndexScanStage(UUID collUuid,
boost::optional<value::SlotId> recordSlot,
boost::optional<value::SlotId> recordIdSlot,
boost::optional<value::SlotId> snapshotIdSlot,
- boost::optional<value::SlotId> indexIdSlot,
IndexKeysInclusionSet indexKeysToInclude,
value::SlotVector vars,
boost::optional<value::SlotId> seekKeySlotLow,
@@ -59,7 +58,6 @@ IndexScanStage::IndexScanStage(UUID collUuid,
_recordSlot(recordSlot),
_recordIdSlot(recordIdSlot),
_snapshotIdSlot(snapshotIdSlot),
- _indexIdSlot(indexIdSlot),
_indexKeysToInclude(indexKeysToInclude),
_vars(std::move(vars)),
_seekKeySlotLow(seekKeySlotLow),
@@ -78,7 +76,6 @@ std::unique_ptr<PlanStage> IndexScanStage::clone() const {
_recordSlot,
_recordIdSlot,
_snapshotIdSlot,
- _indexIdSlot,
_indexKeysToInclude,
_vars,
_seekKeySlotLow,
@@ -131,17 +128,10 @@ void IndexScanStage::prepare(CompileCtx& ctx) {
static_cast<bool>(entry));
_ordering = entry->ordering();
- auto [indexIdTag, indexIdVal] = value::makeNewString(StringData(_indexName));
- _indexIdAccessor.reset(indexIdTag, indexIdVal);
-
- if (_indexIdSlot) {
- _indexIdViewAccessor.reset(indexIdTag, indexIdVal);
- } else {
- _indexIdViewAccessor.reset();
- }
-
if (_snapshotIdAccessor) {
- _latestSnapshotId = _opCtx->recoveryUnit()->getSnapshotId().toNumber();
+ _snapshotIdAccessor->reset(
+ value::TypeTags::NumberInt64,
+ value::bitcastFrom<uint64_t>(_opCtx->recoveryUnit()->getSnapshotId().toNumber()));
}
}
@@ -158,10 +148,6 @@ value::SlotAccessor* IndexScanStage::getAccessor(CompileCtx& ctx, value::SlotId
return _snapshotIdAccessor.get();
}
- if (_indexIdSlot && *_indexIdSlot == slot) {
- return &_indexIdViewAccessor;
- }
-
if (auto it = _accessorMap.find(slot); it != _accessorMap.end()) {
return it->second;
}
@@ -233,7 +219,9 @@ void IndexScanStage::doRestoreState(bool relinquishCursor) {
// Yield is the only time during plan execution that the snapshotId can change. As such, we
// update it accordingly as part of yield recovery.
if (_snapshotIdAccessor) {
- _latestSnapshotId = _opCtx->recoveryUnit()->getSnapshotId().toNumber();
+ _snapshotIdAccessor->reset(
+ value::TypeTags::NumberInt64,
+ value::bitcastFrom<uint64_t>(_opCtx->recoveryUnit()->getSnapshotId().toNumber()));
}
}
@@ -397,12 +385,6 @@ PlanState IndexScanStage::getNext() {
false, value::TypeTags::RecordId, value::bitcastFrom<RecordId*>(&_nextRecord->loc));
}
- if (_snapshotIdAccessor) {
- // Copy the latest snapshot ID into the 'snapshotId' slot.
- _snapshotIdAccessor->reset(value::TypeTags::NumberInt64,
- value::bitcastFrom<uint64_t>(_latestSnapshotId));
- }
-
if (_accessors.size()) {
_valuesBuffer.reset();
readKeyStringValueIntoAccessors(
@@ -441,9 +423,6 @@ std::unique_ptr<PlanStageStats> IndexScanStage::getStats(bool includeDebugInfo)
if (_snapshotIdSlot) {
bob.appendNumber("snapshotIdSlot", static_cast<long long>(*_snapshotIdSlot));
}
- if (_indexIdSlot) {
- bob.appendNumber("indexIdSlot", static_cast<long long>(*_indexIdSlot));
- }
if (_seekKeySlotLow) {
bob.appendNumber("seekKeySlotLow", static_cast<long long>(*_seekKeySlotLow));
}
@@ -492,12 +471,6 @@ std::vector<DebugPrinter::Block> IndexScanStage::debugPrint() const {
DebugPrinter::addIdentifier(ret, DebugPrinter::kNoneKeyword);
}
- if (_indexIdSlot) {
- DebugPrinter::addIdentifier(ret, _indexIdSlot.value());
- } else {
- DebugPrinter::addIdentifier(ret, DebugPrinter::kNoneKeyword);
- }
-
ret.emplace_back(DebugPrinter::Block("[`"));
size_t varIndex = 0;
for (size_t keyIndex = 0; keyIndex < _indexKeysToInclude.size(); ++keyIndex) {
diff --git a/src/mongo/db/exec/sbe/stages/ix_scan.h b/src/mongo/db/exec/sbe/stages/ix_scan.h
index 33fcd352d0c..ce00ef17128 100644
--- a/src/mongo/db/exec/sbe/stages/ix_scan.h
+++ b/src/mongo/db/exec/sbe/stages/ix_scan.h
@@ -51,8 +51,7 @@ namespace mongo::sbe {
* The "output" slots are
* - 'recordSlot': the "KeyString" representing the index entry,
* - 'recordIdSlot': a reference that can be used to fetch the entire document,
- * - 'snapshotIdSlot': the storage snapshot that this index scan is reading from,
- * - 'indexIdSlot': the name of the index being read from, and
+ * - 'snapshotIdSlot': the storage snapshot that this index scan is reading from, and
* - 'vars': one slot for each value in the index key that should be "projected" out of the entry.
*
* The 'indexKeysToInclude' bitset determines which values are included in the projection based
@@ -64,11 +63,10 @@ namespace mongo::sbe {
*
* Debug string representation:
*
- * ixscan recordSlot? recordIdSlot? snapshotIdSlot? indexIdSlot?
- * [slot_1 = fieldNo_1, ..., slot2 = fieldNo_n]
+ * ixscan recordSlot? recordIdSlot? snapshotIdSlot? [slot_1 = fieldNo_1, ..., slot2 = fieldNo_n]
* collectionUuid indexName forward
*
- * ixseek lowKey highKey recordSlot? recordIdSlot? snapshotIdSlot? indexIdSlot?
+ * ixseek lowKey highKey recordSlot? recordIdSlot? snapshotIdSlot?
* [slot_1 = fieldNo_1, ..., slot2 = fieldNo_n]
* collectionUuid indexName forward
*/
@@ -80,7 +78,6 @@ public:
boost::optional<value::SlotId> recordSlot,
boost::optional<value::SlotId> recordIdSlot,
boost::optional<value::SlotId> snapshotIdSlot,
- boost::optional<value::SlotId> indexIdSlot,
IndexKeysInclusionSet indexKeysToInclude,
value::SlotVector vars,
boost::optional<value::SlotId> seekKeySlotLow,
@@ -128,7 +125,6 @@ private:
const boost::optional<value::SlotId> _recordSlot;
const boost::optional<value::SlotId> _recordIdSlot;
const boost::optional<value::SlotId> _snapshotIdSlot;
- const boost::optional<value::SlotId> _indexIdSlot;
const IndexKeysInclusionSet _indexKeysToInclude;
const value::SlotVector _vars;
const boost::optional<value::SlotId> _seekKeySlotLow;
@@ -145,14 +141,6 @@ private:
std::unique_ptr<value::OwnedValueAccessor> _recordIdAccessor;
std::unique_ptr<value::OwnedValueAccessor> _snapshotIdAccessor;
- value::OwnedValueAccessor _indexIdAccessor;
- value::ViewOfValueAccessor _indexIdViewAccessor;
-
- // This field holds the latest snapshot ID that we've received from _opCtx->recoveryUnit().
- // This field gets initialized by prepare(), and it gets updated each time doRestoreState() is
- // called.
- uint64_t _latestSnapshotId{0};
-
// One accessor and slot for each key component that this stage will bind from an index entry's
// KeyString. The accessors are in the same order as the key components they bind to.
std::vector<value::OwnedValueAccessor> _accessors;
diff --git a/src/mongo/db/exec/sbe/stages/plan_stats.h b/src/mongo/db/exec/sbe/stages/plan_stats.h
index f487c7a45d2..263a4f86660 100644
--- a/src/mongo/db/exec/sbe/stages/plan_stats.h
+++ b/src/mongo/db/exec/sbe/stages/plan_stats.h
@@ -278,13 +278,8 @@ struct HashAggStats : public SpecificStats {
}
bool usedDisk{false};
- // The number of times that the entire hash table was spilled.
- long long numSpills{0};
- // The number of individual records spilled to disk.
long long spilledRecords{0};
- // An estimate, in bytes, of the size of the final spill table after all spill events have taken
- // place.
- long long spilledDataStorageSize{0};
+ long long lastSpilledRecordSize{0};
};
struct HashLookupStats : public SpecificStats {
diff --git a/src/mongo/db/exec/sbe/stages/scan.cpp b/src/mongo/db/exec/sbe/stages/scan.cpp
index 3d601d8779e..678d3f84ef9 100644
--- a/src/mongo/db/exec/sbe/stages/scan.cpp
+++ b/src/mongo/db/exec/sbe/stages/scan.cpp
@@ -209,7 +209,6 @@ void ScanStage::doSaveState(bool relinquishCursor) {
cursor->setSaveStorageCursorOnDetachFromOperationContext(!relinquishCursor);
}
- _indexCatalogEntryMap.clear();
_coll.reset();
}
@@ -392,14 +391,9 @@ PlanState ScanStage::getNext() {
}
// Return EOF if the index key is found to be inconsistent.
- if (_scanCallbacks.indexKeyConsistencyCheckCallback &&
- !_scanCallbacks.indexKeyConsistencyCheckCallback(_opCtx,
- _indexCatalogEntryMap,
- _snapshotIdAccessor,
- _indexIdAccessor,
- _indexKeyAccessor,
- _coll,
- *nextRecord)) {
+ if (_scanCallbacks.indexKeyConsistencyCheckCallBack &&
+ !_scanCallbacks.indexKeyConsistencyCheckCallBack(
+ _opCtx, _snapshotIdAccessor, _indexIdAccessor, _indexKeyAccessor, _coll, *nextRecord)) {
return trackPlanState(PlanState::IS_EOF);
}
@@ -463,7 +457,6 @@ void ScanStage::close() {
auto optTimer(getOptTimer(_opCtx));
trackClose();
- _indexCatalogEntryMap.clear();
_cursor.reset();
_randomCursor.reset();
_coll.reset();
@@ -749,7 +742,6 @@ void ParallelScanStage::doSaveState(bool relinquishCursor) {
_cursor->save();
}
- _indexCatalogEntryMap.clear();
_coll.reset();
}
@@ -907,9 +899,8 @@ PlanState ParallelScanStage::getNext() {
}
// Return EOF if the index key is found to be inconsistent.
- if (_scanCallbacks.indexKeyConsistencyCheckCallback &&
- !_scanCallbacks.indexKeyConsistencyCheckCallback(_opCtx,
- _indexCatalogEntryMap,
+ if (_scanCallbacks.indexKeyConsistencyCheckCallBack &&
+ !_scanCallbacks.indexKeyConsistencyCheckCallBack(_opCtx,
_snapshotIdAccessor,
_indexIdAccessor,
_indexKeyAccessor,
@@ -965,7 +956,6 @@ void ParallelScanStage::close() {
auto optTimer(getOptTimer(_opCtx));
trackClose();
- _indexCatalogEntryMap.clear();
_cursor.reset();
_coll.reset();
_open = false;
diff --git a/src/mongo/db/exec/sbe/stages/scan.h b/src/mongo/db/exec/sbe/stages/scan.h
index 6f4980f52e1..37462ac5e14 100644
--- a/src/mongo/db/exec/sbe/stages/scan.h
+++ b/src/mongo/db/exec/sbe/stages/scan.h
@@ -45,11 +45,11 @@ struct ScanCallbacks {
IndexKeyConsistencyCheckCallback indexKeyConsistencyCheck = {},
ScanOpenCallback scanOpen = {})
: indexKeyCorruptionCheckCallback(std::move(indexKeyCorruptionCheck)),
- indexKeyConsistencyCheckCallback(std::move(indexKeyConsistencyCheck)),
+ indexKeyConsistencyCheckCallBack(std::move(indexKeyConsistencyCheck)),
scanOpenCallback(std::move(scanOpen)) {}
IndexKeyCorruptionCheckCallback indexKeyCorruptionCheckCallback;
- IndexKeyConsistencyCheckCallback indexKeyConsistencyCheckCallback;
+ IndexKeyConsistencyCheckCallback indexKeyConsistencyCheckCallBack;
ScanOpenCallback scanOpenCallback;
};
@@ -83,12 +83,12 @@ struct ScanCallbacks {
*
* Debug string representations:
*
- * scan recordSlot? recordIdSlot? snapshotIdSlot? indexIdSlot? indexKeySlot?
- * indexKeyPatternSlot? [slot1 = fieldName1, ... slot_n = fieldName_n] collectionUuid
+ * scan recordSlot|none recordIdSlot|none snapshotIdSlot|none indexIdSlot|none indexKeySlot|none
+ * indexKeyPatternSlot|none [slot1 = fieldName1, ... slot_n = fieldName_n] collectionUuid
* forward needOplogSlotForTs
*
- * seek seekKeySlot recordSlot? recordIdSlot? snapshotIdSlot? indexIdSlot? indexKeySlot?
- * indexKeyPatternSlot? [slot1 = fieldName1, ... slot_n = fieldName_n]
+ * seek seekKeySlot recordSlot|none recordIdSlot|none snapshotIdSlot|none indexIdSlot|none
+ * indexKeySlot|none indexKeyPatternSlot|none [slot1 = fieldName1, ... slot_n = fieldName_n]
* collectionUuid forward needOplogSlotForTs
*/
class ScanStage final : public PlanStage {
@@ -197,8 +197,6 @@ private:
// collection is still valid. Only relevant to capped collections.
bool _needsToCheckCappedPositionLost = false;
- StringMap<const IndexCatalogEntry*> _indexCatalogEntryMap;
-
#if defined(MONGO_CONFIG_DEBUG_BUILD)
// Debug-only buffer used to track the last thing returned from the stage. Between
// saves/restores this is used to check that the storage cursor has not changed position.
@@ -313,8 +311,6 @@ private:
std::unique_ptr<SeekableRecordCursor> _cursor;
- StringMap<const IndexCatalogEntry*> _indexCatalogEntryMap;
-
#if defined(MONGO_CONFIG_DEBUG_BUILD)
// Debug-only buffer used to track the last thing returned from the stage. Between
// saves/restores this is used to check that the storage cursor has not changed position.
diff --git a/src/mongo/db/exec/sbe/stages/sort.cpp b/src/mongo/db/exec/sbe/stages/sort.cpp
index eebdc0c2443..5acf73afe8d 100644
--- a/src/mongo/db/exec/sbe/stages/sort.cpp
+++ b/src/mongo/db/exec/sbe/stages/sort.cpp
@@ -213,11 +213,11 @@ void SortStage::open(bool reOpen) {
_specificStats.totalDataSizeBytes += _sorter->totalDataSizeSorted();
_mergeIt.reset(_sorter->done());
- _specificStats.spills += _sorter->stats().spilledRanges();
+ _specificStats.spills += _sorter->numSpills();
_specificStats.keysSorted += _sorter->numSorted();
auto& metricsCollector = ResourceConsumption::MetricsCollector::get(_opCtx);
metricsCollector.incrementKeysSorted(_sorter->numSorted());
- metricsCollector.incrementSorterSpills(_sorter->stats().spilledRanges());
+ metricsCollector.incrementSorterSpills(_sorter->numSpills());
_children[0]->close();
}
diff --git a/src/mongo/db/exec/sbe/stages/union.cpp b/src/mongo/db/exec/sbe/stages/union.cpp
index 3ddadb8c912..a661e6c579f 100644
--- a/src/mongo/db/exec/sbe/stages/union.cpp
+++ b/src/mongo/db/exec/sbe/stages/union.cpp
@@ -62,30 +62,28 @@ std::unique_ptr<PlanStage> UnionStage::clone() const {
}
void UnionStage::prepare(CompileCtx& ctx) {
+ value::SlotSet dupCheck;
+
for (size_t childNum = 0; childNum < _children.size(); childNum++) {
_children[childNum]->prepare(ctx);
}
- // All of the slots listed in '_outputVals' must be unique.
- value::SlotSet dupCheck;
- for (auto slot : _outputVals) {
- auto [it, inserted] = dupCheck.insert(slot);
- uassert(4822807, str::stream() << "duplicate field: " << slot, inserted);
- }
-
for (size_t idx = 0; idx < _outputVals.size(); ++idx) {
std::vector<value::SlotAccessor*> accessors;
accessors.reserve(_children.size());
for (size_t childNum = 0; childNum < _children.size(); childNum++) {
- // Slots listed in '_inputVals' may not appear in '_outputVals'.
auto slot = _inputVals[childNum][idx];
- bool slotFound = dupCheck.count(slot);
- uassert(4822806, str::stream() << "duplicate field: " << slot, !slotFound);
+ auto [it, inserted] = dupCheck.insert(slot);
+ uassert(4822806, str::stream() << "duplicate field: " << slot, inserted);
accessors.emplace_back(_children[childNum]->getAccessor(ctx, slot));
}
+ auto slot = _outputVals[idx];
+ auto [it, inserted] = dupCheck.insert(slot);
+ uassert(4822807, str::stream() << "duplicate field: " << slot, inserted);
+
_outValueAccessors.emplace_back(value::SwitchAccessor{std::move(accessors)});
}
}
diff --git a/src/mongo/db/exec/sbe/util/spilling.cpp b/src/mongo/db/exec/sbe/util/spilling.cpp
index 7675fad6846..c54f3bfe956 100644
--- a/src/mongo/db/exec/sbe/util/spilling.cpp
+++ b/src/mongo/db/exec/sbe/util/spilling.cpp
@@ -29,18 +29,6 @@
#include "mongo/db/exec/sbe/util/spilling.h"
-#include "mongo/base/status.h"
-#include "mongo/base/status_with.h"
-#include "mongo/base/string_data.h"
-#include "mongo/bson/timestamp.h"
-#include "mongo/db/query/query_knobs_gen.h"
-#include "mongo/db/storage/record_data.h"
-#include "mongo/db/storage/recovery_unit.h"
-#include "mongo/db/storage/write_unit_of_work.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/bufreader.h"
-#include "mongo/util/str.h"
-
namespace mongo {
namespace sbe {
@@ -69,136 +57,56 @@ KeyString::Value decodeKeyString(const RecordId& rid, KeyString::TypeBits typeBi
return kb.getValueCopy();
}
-SpillingStore::SpillingStore(OperationContext* opCtx, KeyFormat format) {
- _recordStore =
- opCtx->getServiceContext()->getStorageEngine()->makeTemporaryRecordStore(opCtx, format);
-
- _spillingUnit = std::unique_ptr<RecoveryUnit>(
- opCtx->getServiceContext()->getStorageEngine()->newRecoveryUnit());
- _spillingUnit->setCacheMaxWaitTimeout(Milliseconds(internalQuerySpillingMaxWaitTimeout.load()));
- _spillingState = WriteUnitOfWork::RecoveryUnitState::kNotInUnitOfWork;
-}
-
-SpillingStore::~SpillingStore() {}
-
-int SpillingStore::upsertToRecordStore(OperationContext* opCtx,
- const RecordId& recordKey,
- const value::MaterializedRow& key,
- const value::MaterializedRow& val,
- bool update) {
- BufBuilder buf;
- key.serializeForSorter(buf);
- val.serializeForSorter(buf);
- return upsertToRecordStore(opCtx, recordKey, buf, update);
+boost::optional<value::MaterializedRow> readFromRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& rid) {
+ RecordData record;
+ if (rs->findRecord(opCtx, rid, &record)) {
+ auto valueReader = BufReader(record.data(), record.size());
+ return value::MaterializedRow::deserializeForSorter(valueReader, {});
+ }
+ return boost::none;
}
-int SpillingStore::upsertToRecordStore(
- OperationContext* opCtx,
- const RecordId& key,
- const value::MaterializedRow& val,
- const KeyString::TypeBits& typeBits, // recover type of value.
- bool update) {
+int upsertToRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& key,
+ const value::MaterializedRow& val,
+ const KeyString::TypeBits& typeBits, // recover type of value.
+ bool update) {
BufBuilder bufValue;
val.serializeForSorter(bufValue);
- // Append the 'typeBits' to the end of the val's buffer so the 'key' can be reconstructed when
- // draining HashAgg.
- bufValue.appendBuf(typeBits.getBuffer(), typeBits.getSize());
-
- return upsertToRecordStore(opCtx, key, bufValue, update);
+ return upsertToRecordStore(opCtx, rs, key, bufValue, typeBits, update);
}
-int SpillingStore::upsertToRecordStore(
- OperationContext* opCtx,
- const RecordId& key,
- BufBuilder& buf,
- const KeyString::TypeBits& typeBits, // recover type of value.
- bool update) {
+int upsertToRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& key,
+ BufBuilder& buf,
+ const KeyString::TypeBits& typeBits, // recover type of value.
+ bool update) {
+
// Append the 'typeBits' to the end of the val's buffer so the 'key' can be reconstructed when
// draining HashAgg.
buf.appendBuf(typeBits.getBuffer(), typeBits.getSize());
- return upsertToRecordStore(opCtx, key, buf, update);
-}
-
-int SpillingStore::upsertToRecordStore(OperationContext* opCtx,
- const RecordId& key,
- BufBuilder& buf,
- bool update) {
assertIgnorePrepareConflictsBehavior(opCtx);
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
WriteUnitOfWork wuow(opCtx);
auto result = mongo::Status::OK();
if (update) {
- result = rs()->updateRecord(opCtx, key, buf.buf(), buf.len());
+ result = rs->updateRecord(opCtx, key, buf.buf(), buf.len());
} else {
- auto status = rs()->insertRecord(opCtx, key, buf.buf(), buf.len(), Timestamp{});
+ auto status = rs->insertRecord(opCtx, key, buf.buf(), buf.len(), Timestamp{});
result = status.getStatus();
}
wuow.commit();
-
if (!result.isOK()) {
tasserted(5843600, str::stream() << "Failed to write to disk because " << result.reason());
return 0;
}
return buf.len();
}
-
-Status SpillingStore::insertRecords(OperationContext* opCtx,
- std::vector<Record>* inOutRecords,
- const std::vector<Timestamp>& timestamps) {
- assertIgnorePrepareConflictsBehavior(opCtx);
-
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
- WriteUnitOfWork wuow(opCtx);
- auto status = rs()->insertRecords(opCtx, inOutRecords, timestamps);
- wuow.commit();
-
- return status;
-}
-
-boost::optional<value::MaterializedRow> SpillingStore::readFromRecordStore(OperationContext* opCtx,
- const RecordId& rid) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
-
- RecordData record;
- if (rs()->findRecord(opCtx, rid, &record)) {
- auto valueReader = BufReader(record.data(), record.size());
- return value::MaterializedRow::deserializeForSorter(valueReader, {});
- }
- return boost::none;
-}
-
-bool SpillingStore::findRecord(OperationContext* opCtx, const RecordId& loc, RecordData* out) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
-
- return rs()->findRecord(opCtx, loc, out);
-}
-
-void SpillingStore::switchToSpilling(OperationContext* opCtx) {
- invariant(!_originalUnit);
- _originalUnit = opCtx->releaseRecoveryUnit();
- _originalState = opCtx->setRecoveryUnit(std::move(_spillingUnit), _spillingState);
-}
-void SpillingStore::switchToOriginal(OperationContext* opCtx) {
- invariant(!_spillingUnit);
- _spillingUnit = opCtx->releaseRecoveryUnit();
- _spillingState = opCtx->setRecoveryUnit(std::move(_originalUnit), _originalState);
- invariant(!(_spillingUnit->getState() == RecoveryUnit::State::kInactiveInUnitOfWork ||
- _spillingUnit->getState() == RecoveryUnit::State::kActive));
-}
-
-void SpillingStore::saveState() {
- _spillingUnit->abandonSnapshot();
-}
-void SpillingStore::restoreState() {
- // We do not have to do anything.
-}
-
} // namespace sbe
} // namespace mongo
diff --git a/src/mongo/db/exec/sbe/util/spilling.h b/src/mongo/db/exec/sbe/util/spilling.h
index 205d6f1a031..95f73e2b02e 100644
--- a/src/mongo/db/exec/sbe/util/spilling.h
+++ b/src/mongo/db/exec/sbe/util/spilling.h
@@ -29,14 +29,9 @@
#pragma once
-#include <boost/optional/optional.hpp>
-#include <utility>
+#include "mongo/platform/basic.h"
-#include "mongo/bson/util/builder.h"
#include "mongo/db/exec/sbe/values/slot.h"
-#include "mongo/db/operation_context.h"
-#include "mongo/db/record_id.h"
-#include "mongo/db/storage/record_store.h"
#include "mongo/db/storage/temporary_record_store.h"
namespace mongo {
@@ -55,104 +50,27 @@ std::pair<RecordId, KeyString::TypeBits> encodeKeyString(KeyString::Builder&,
// Reconstructs the KeyString carried in RecordId using 'typeBits'.
KeyString::Value decodeKeyString(const RecordId& rid, KeyString::TypeBits typeBits);
-/**
- * SpillingStore is a wrapper around a temporary record store than maintains its own transaction as
- * we do not want to intermingle operations running in the main query with spill reads and writes.
- */
-class SpillingStore {
-public:
- SpillingStore(OperationContext* opCtx, KeyFormat format = KeyFormat::String);
- ~SpillingStore();
-
- /**
- * When a collator is provided, the key is encoded using the collator before being converted to
- * a record id. In this case, it is not possible to recover the key from the record id, thus we
- * need to store the original value of the key as well.
- */
- int upsertToRecordStore(OperationContext* opCtx,
- const RecordId& recordKey,
- const value::MaterializedRow& key,
- const value::MaterializedRow& val,
- bool update);
- /**
- * Inserts or updates a key/value into 'rs'. The 'update' flag controls whether or not an update
- * will be performed. If a key/value pair is inserted into the 'rs' that already exists and
- * 'update' is false, this function will tassert.
- *
- * Returns the size of the new record in bytes, including the record id and value portions.
- */
- int upsertToRecordStore(OperationContext* opCtx,
- const RecordId& key,
- const value::MaterializedRow& val,
- const KeyString::TypeBits& typeBits,
- bool update);
- int upsertToRecordStore(OperationContext* opCtx,
- const RecordId& key,
- BufBuilder& buf,
- const KeyString::TypeBits& typeBits, // recover type of value.
- bool update);
- int upsertToRecordStore(OperationContext* opCtx,
- const RecordId& key,
- BufBuilder& buf,
- bool update);
-
-
- Status insertRecords(OperationContext* opCtx,
- std::vector<Record>* inOutRecords,
- const std::vector<Timestamp>& timestamps);
-
- // Reads a materialized row from the record store.
- boost::optional<value::MaterializedRow> readFromRecordStore(OperationContext* opCtx,
- const RecordId& rid);
-
- bool findRecord(OperationContext* opCtx, const RecordId& loc, RecordData* out);
-
- auto rs() {
- return _recordStore->rs();
- }
-
- auto getCursor(OperationContext* opCtx) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
- return rs()->getCursor(opCtx);
- }
-
- void resetCursor(OperationContext* opCtx, std::unique_ptr<SeekableRecordCursor>& cursor) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
- cursor.reset();
- }
+// Reads a materialized row from the record store.
+boost::optional<value::MaterializedRow> readFromRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& rid);
- auto saveCursor(OperationContext* opCtx, std::unique_ptr<SeekableRecordCursor>& cursor) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
-
- return cursor->save();
- }
-
- auto restoreCursor(OperationContext* opCtx, std::unique_ptr<SeekableRecordCursor>& cursor) {
- switchToSpilling(opCtx);
- ON_BLOCK_EXIT([&] { switchToOriginal(opCtx); });
-
- return cursor->restore();
- }
-
- void saveState();
- void restoreState();
-
-private:
- void switchToSpilling(OperationContext* opCtx);
- void switchToOriginal(OperationContext* opCtx);
-
- std::unique_ptr<TemporaryRecordStore> _recordStore;
-
- std::unique_ptr<RecoveryUnit> _originalUnit;
- WriteUnitOfWork::RecoveryUnitState _originalState;
-
- std::unique_ptr<RecoveryUnit> _spillingUnit;
- WriteUnitOfWork::RecoveryUnitState _spillingState;
-
- size_t _counter{0};
-};
+/** Inserts or updates a key/value into 'rs'. The 'update' flag controls whether or not an update
+ * will be performed. If a key/value pair is inserted into the 'rs' that already exists and
+ * 'update' is false, this function will tassert.
+ */
+int upsertToRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& key,
+ const value::MaterializedRow& val,
+ const KeyString::TypeBits& typeBits,
+ bool update);
+
+int upsertToRecordStore(OperationContext* opCtx,
+ RecordStore* rs,
+ const RecordId& key,
+ BufBuilder& buf,
+ const KeyString::TypeBits& typeBits, // recover type of value.
+ bool update);
} // namespace sbe
} // namespace mongo
diff --git a/src/mongo/db/exec/sbe/values/slot.cpp b/src/mongo/db/exec/sbe/values/slot.cpp
index 93605a9abfa..45cbc977980 100644
--- a/src/mongo/db/exec/sbe/values/slot.cpp
+++ b/src/mongo/db/exec/sbe/values/slot.cpp
@@ -42,9 +42,7 @@
#include "mongo/util/bufreader.h"
namespace mongo::sbe::value {
-
-static std::pair<TypeTags, Value> deserializeValue(BufReader& buf,
- const CollatorInterface* collator) {
+static std::pair<TypeTags, Value> deserializeValue(BufReader& buf) {
auto tag = static_cast<TypeTags>(buf.read<uint8_t>());
Value val;
@@ -109,7 +107,7 @@ static std::pair<TypeTags, Value> deserializeValue(BufReader& buf,
if (cnt) {
arr->reserve(cnt);
for (size_t idx = 0; idx < cnt; ++idx) {
- auto [tag, val] = deserializeValue(buf, collator);
+ auto [tag, val] = deserializeValue(buf);
arr->push_back(tag, val);
}
}
@@ -118,16 +116,13 @@ static std::pair<TypeTags, Value> deserializeValue(BufReader& buf,
break;
}
case TypeTags::ArraySet: {
- // The first byte is a flag to tell us whether the ArraySet had a collation prior to
- // serialization.
- auto collated = buf.read<char>();
- auto [arrTag, arrVal] = makeNewArraySet(collated ? collator : nullptr);
auto cnt = buf.read<LittleEndian<size_t>>();
+ auto [arrTag, arrVal] = makeNewArraySet();
auto arr = getArraySetView(arrVal);
if (cnt) {
arr->reserve(cnt);
for (size_t idx = 0; idx < cnt; ++idx) {
- auto [tag, val] = deserializeValue(buf, collator);
+ auto [tag, val] = deserializeValue(buf);
arr->push_back(tag, val);
}
}
@@ -143,7 +138,7 @@ static std::pair<TypeTags, Value> deserializeValue(BufReader& buf,
obj->reserve(cnt);
for (size_t idx = 0; idx < cnt; ++idx) {
auto fieldName = buf.readCStr();
- auto [tag, val] = deserializeValue(buf, collator);
+ auto [tag, val] = deserializeValue(buf);
obj->push_back(fieldName, tag, val);
}
}
@@ -218,12 +213,12 @@ static std::pair<TypeTags, Value> deserializeValue(BufReader& buf,
}
MaterializedRow MaterializedRow::deserializeForSorter(BufReader& buf,
- const SorterDeserializeSettings& settings) {
+ const SorterDeserializeSettings&) {
auto cnt = buf.read<LittleEndian<size_t>>();
MaterializedRow result{cnt};
for (size_t idx = 0; idx < cnt; ++idx) {
- auto [tag, val] = deserializeValue(buf, settings.collator);
+ auto [tag, val] = deserializeValue(buf);
result.reset(idx, true, tag, val);
}
@@ -293,12 +288,6 @@ static void serializeValue(BufBuilder& buf, TypeTags tag, Value val) {
}
case TypeTags::ArraySet: {
auto arr = getArraySetView(val);
- // If an ArraySet has a collation, we serialize a byte which acts as a flag as to
- // whether the set should be created with a collation upon deserialization. Also, we
- // assume that the caller which does deserialization will have the context about what
- // the collation is, and therefore we can save space by not serializing the full
- // description of the collation.
- buf.appendChar(arr->getCollator() ? 1 : 0);
buf.appendNum(arr->size());
for (auto& kv : arr->values()) {
serializeValue(buf, kv.first, kv.second);
@@ -382,22 +371,7 @@ static void serializeValue(BufBuilder& buf, TypeTags tag, Value val) {
}
}
-/**
- * If non-null 'collator' is provided during serialization, then the encoding guarantees that values
- * which are equal up to the collation will encode to the same result, allowing for collation-aware
- * equality comparisons. However, the collator-aware encoded values are not always decodable. Groups
- * store an original copy of the group key alongside the encoded key string when there is a
- * collation, so the key string does not need to be decodable.
- */
-static void serializeValueIntoKeyString(KeyString::Builder& buf,
- TypeTags tag,
- Value val,
- const CollatorInterface* collator) {
-
- const auto stringTransformFn = [&](StringData stringData) {
- return collator->getComparisonString(stringData);
- };
-
+static void serializeValueIntoKeyString(KeyString::Builder& buf, TypeTags tag, Value val) {
switch (tag) {
case TypeTags::Nothing: {
buf.appendBool(false);
@@ -460,21 +434,19 @@ static void serializeValueIntoKeyString(KeyString::Builder& buf,
buf.appendUndefined();
break;
}
- case TypeTags::StringSmall:
+ case TypeTags::StringSmall: {
+ // Small strings cannot contain null bytes, so it is safe to serialize them as plain
+ // C-strings with a null terminator.
+ buf.appendBool(true);
+ buf.appendString(getStringView(tag, val));
+ break;
+ }
case TypeTags::StringBig:
case TypeTags::bsonString: {
buf.appendBool(true);
- if (collator) {
- buf.appendString(getStringView(tag, val), stringTransformFn);
- } else {
- buf.appendString(getStringView(tag, val));
- }
+ buf.appendString(getStringOrSymbolView(tag, val));
break;
}
- // Note that the collation would have to apply to bsonSymbol values to match the
- // behavior of the Classic engine when spilling groups to disk. bsonSymbol is
- // deprecated, however, and SBE no longer provides strict correctness guarantees about
- // computations on bsonSymbol values.
case TypeTags::bsonSymbol: {
buf.appendBool(true);
buf.appendSymbol(getStringOrSymbolView(tag, val));
@@ -485,13 +457,9 @@ static void serializeValueIntoKeyString(KeyString::Builder& buf,
// TODO SERVER-61629: convert this to serialize the 'arr' directly instead of
// constructing a BSONArray.
BSONArrayBuilder builder;
- bson::convertToBsonObj(builder, value::ArrayEnumerator{tag, val});
+ bson::convertToBsonObj(builder, getArrayView(val));
buf.appendBool(true);
- if (collator) {
- buf.appendArray(builder.arr(), stringTransformFn);
- } else {
- buf.appendArray(builder.arr());
- }
+ buf.appendArray(BSONArray(builder.done()));
break;
}
case TypeTags::Object: {
@@ -500,37 +468,27 @@ static void serializeValueIntoKeyString(KeyString::Builder& buf,
BSONObjBuilder builder;
bson::convertToBsonObj(builder, getObjectView(val));
buf.appendBool(true);
- if (collator) {
- buf.appendObject(builder.done(), stringTransformFn);
- } else {
- buf.appendObject(builder.done());
- }
+ buf.appendObject(builder.done());
break;
}
- case TypeTags::bsonObjectId:
case TypeTags::ObjectId: {
buf.appendBool(true);
- buf.appendOID(OID::from(getRawPointerView(val)));
+ buf.appendBytes(getObjectIdView(val), sizeof(ObjectIdType));
break;
}
case TypeTags::bsonObject: {
- BSONObj bson{getRawPointerView(val)};
buf.appendBool(true);
- if (collator) {
- buf.appendObject(bson, stringTransformFn);
- } else {
- buf.appendObject(bson);
- }
+ buf.appendObject(BSONObj(getRawPointerView(val)));
break;
}
case TypeTags::bsonArray: {
- BSONObj bson{getRawPointerView(val)};
buf.appendBool(true);
- if (collator) {
- buf.appendArray(BSONArray(BSONObj(bson)), stringTransformFn);
- } else {
- buf.appendArray(BSONArray(BSONObj(bson)));
- }
+ buf.appendArray(BSONArray(BSONObj(getRawPointerView(val))));
+ break;
+ }
+ case TypeTags::bsonObjectId: {
+ buf.appendBool(true);
+ buf.appendOID(OID::from(getRawPointerView(val)));
break;
}
case TypeTags::bsonBinData: {
@@ -599,18 +557,15 @@ void MaterializedRow::serializeForSorter(BufBuilder& buf) const {
}
}
-void MaterializedRow::serializeIntoKeyString(KeyString::Builder& buf,
- const CollatorInterface* collator) const {
+void MaterializedRow::serializeIntoKeyString(KeyString::Builder& buf) const {
for (size_t idx = 0; idx < size(); ++idx) {
auto [tag, val] = getViewOfValue(idx);
- serializeValueIntoKeyString(buf, tag, val, collator);
+ serializeValueIntoKeyString(buf, tag, val);
}
}
-MaterializedRow MaterializedRow::deserializeFromKeyString(
- const KeyString::Value& keyString,
- BufBuilder* valueBufferBuilder,
- boost::optional<size_t> numPrefixValsToRead) {
+MaterializedRow MaterializedRow::deserializeFromKeyString(const KeyString::Value& keyString,
+ BufBuilder* valueBufferBuilder) {
BufReader reader(keyString.getBuffer(), keyString.getSize());
KeyString::TypeBits typeBits(keyString.getTypeBits());
KeyString::TypeBits::Reader typeBitsReader(typeBits);
@@ -622,8 +577,7 @@ MaterializedRow MaterializedRow::deserializeFromKeyString(
&reader, &typeBitsReader, false /* inverted */, typeBits.version, &valBuilder);
} while (keepReading);
- size_t sizeOfRow = numPrefixValsToRead ? *numPrefixValsToRead : valBuilder.numValues();
- MaterializedRow result{sizeOfRow};
+ MaterializedRow result{valBuilder.numValues()};
valBuilder.readValues(result);
return result;
diff --git a/src/mongo/db/exec/sbe/values/slot.h b/src/mongo/db/exec/sbe/values/slot.h
index e682b2bfeb8..f853f816d4d 100644
--- a/src/mongo/db/exec/sbe/values/slot.h
+++ b/src/mongo/db/exec/sbe/values/slot.h
@@ -468,11 +468,8 @@ public:
}
// The following methods are used by the sorter only.
- struct SorterDeserializeSettings {
- const CollatorInterface* collator{nullptr};
- };
- static MaterializedRow deserializeForSorter(BufReader& buf,
- const SorterDeserializeSettings& settings);
+ struct SorterDeserializeSettings {};
+ static MaterializedRow deserializeForSorter(BufReader& buf, const SorterDeserializeSettings&);
void serializeForSorter(BufBuilder& buf) const;
int memUsageForSorter() const;
auto getOwned() const {
@@ -486,21 +483,11 @@ public:
* intended for spilling key values used in the HashAgg stage. The format is not guaranteed to
* be stable between versions, so it should not be used for long-term storage or communication
* between instances.
- *
- * If 'numPrefixValsToRead' is provided, then only the given number of values from 'keyString'
- * are decoded into the resulting 'MaterializedRow'. The remaining suffix values in the
- * 'keyString' are ignored.
- *
- * If non-null 'collator' is provided during serialization, then any strings in the row are
- * encoded as ICU collation keys prior to being KeyString-encoded.
*/
- static MaterializedRow deserializeFromKeyString(
- const KeyString::Value& keyString,
- BufBuilder* valueBufferBuilder,
- boost::optional<size_t> numPrefixValsToRead = boost::none);
+ static MaterializedRow deserializeFromKeyString(const KeyString::Value& keyString,
- void serializeIntoKeyString(KeyString::Builder& builder,
- const CollatorInterface* collator = nullptr) const;
+ BufBuilder* valueBufferBuilder);
+ void serializeIntoKeyString(KeyString::Builder& builder) const;
private:
static size_t sizeInBytes(size_t count) {
@@ -590,9 +577,9 @@ private:
};
struct MaterializedRowEq {
- using ComparatorType = StringData::ComparatorInterface;
+ using ComparatorType = StringData::ComparatorInterface*;
- explicit MaterializedRowEq(const ComparatorType* comparator = nullptr)
+ explicit MaterializedRowEq(const ComparatorType comparator = nullptr)
: _comparator(comparator) {}
bool operator()(const MaterializedRow& lhs, const MaterializedRow& rhs) const {
@@ -610,7 +597,7 @@ struct MaterializedRowEq {
}
private:
- const ComparatorType* _comparator = nullptr;
+ const ComparatorType _comparator = nullptr;
};
struct MaterializedRowLess {
diff --git a/src/mongo/db/exec/sbe/values/value.cpp b/src/mongo/db/exec/sbe/values/value.cpp
index e0f73c87ddf..6c21f4f6e5d 100644
--- a/src/mongo/db/exec/sbe/values/value.cpp
+++ b/src/mongo/db/exec/sbe/values/value.cpp
@@ -860,7 +860,7 @@ bool isInfinity(TypeTags tag, Value val) {
(tag == TypeTags::NumberDecimal && bitcastTo<Decimal128>(val).isInfinite());
}
-bool ArraySet::push_back(TypeTags tag, Value val) {
+void ArraySet::push_back(TypeTags tag, Value val) {
if (tag != TypeTags::Nothing) {
ValueGuard guard{tag, val};
auto [it, inserted] = _values.insert({tag, val});
@@ -868,11 +868,7 @@ bool ArraySet::push_back(TypeTags tag, Value val) {
if (inserted) {
guard.reset();
}
-
- return inserted;
}
-
- return false;
}
std::pair<TypeTags, Value> ArrayEnumerator::getViewOfValue() const {
diff --git a/src/mongo/db/exec/sbe/values/value.h b/src/mongo/db/exec/sbe/values/value.h
index ae207106d8b..26ba3e5e1de 100644
--- a/src/mongo/db/exec/sbe/values/value.h
+++ b/src/mongo/db/exec/sbe/values/value.h
@@ -844,14 +844,7 @@ public:
}
}
- /**
- * Adds the given SBE value to the set if an equal value is not already present. Assumes
- * ownership of the given value.
- *
- * Returns true if the value was newly inserted, otherwise returns false to indicate that an
- * equal value was already present in the set.
- */
- bool push_back(TypeTags tag, Value val);
+ void push_back(TypeTags tag, Value val);
auto& values() const noexcept {
return _values;
diff --git a/src/mongo/db/exec/sbe/values/value_builder.h b/src/mongo/db/exec/sbe/values/value_builder.h
index 00333e9f824..9ad2b511242 100644
--- a/src/mongo/db/exec/sbe/values/value_builder.h
+++ b/src/mongo/db/exec/sbe/values/value_builder.h
@@ -191,11 +191,8 @@ public:
virtual size_t numValues() const = 0;
protected:
- // We expect most rows to end up containing this many values or fewer.
- static constexpr int kInlinedVectorSize = 16;
-
std::pair<TypeTags, Value> getValue(size_t index, int bufferLen) {
- invariant(index < _tagList.size());
+ invariant(index < _numValues);
auto tag = _tagList[index];
auto val = _valList[index];
@@ -227,8 +224,9 @@ protected:
}
void appendValue(TypeTags tag, Value val) noexcept {
- _tagList.push_back(tag);
- _valList.push_back(val);
+ _tagList[_numValues] = tag;
+ _valList[_numValues] = val;
+ ++_numValues;
}
void appendValue(std::pair<TypeTags, Value> in) noexcept {
@@ -243,12 +241,14 @@ protected:
// storing a pointer, we store an _offset_ into the under-construction buffer. Translation from
// offset to pointer occurs as part of the 'readValues()' function.
void appendValueBufferOffset(TypeTags tag) {
- _tagList.push_back(tag);
- _valList.push_back(value::bitcastFrom<int32_t>(_valueBufferBuilder->len()));
+ _tagList[_numValues] = tag;
+ _valList[_numValues] = value::bitcastFrom<int32_t>(_valueBufferBuilder->len());
+ ++_numValues;
}
- absl::InlinedVector<TypeTags, kInlinedVectorSize> _tagList;
- absl::InlinedVector<Value, kInlinedVectorSize> _valList;
+ std::array<TypeTags, Ordering::kMaxCompoundIndexKeys> _tagList;
+ std::array<Value, Ordering::kMaxCompoundIndexKeys> _valList;
+ size_t _numValues = 0;
BufBuilder* _valueBufferBuilder;
};
@@ -270,12 +270,11 @@ public:
// buffer, this value will remain in that buffer, even though we've removed it from the
// list. It will still get deallocated along with everything else when that buffer gets
// cleared or deleted, though, so there is no leak.
- _tagList.pop_back();
- _valList.pop_back();
+ --_numValues;
}
size_t numValues() const override {
- return _tagList.size();
+ return _numValues;
}
/**
@@ -285,7 +284,7 @@ public:
*/
void readValues(std::vector<OwnedValueAccessor>* accessors) {
auto bufferLen = _valueBufferBuilder->len();
- for (size_t i = 0; i < _tagList.size(); ++i) {
+ for (size_t i = 0; i < _numValues; ++i) {
auto [tag, val] = getValue(i, bufferLen);
invariant(i < accessors->size());
(*accessors)[i].reset(false, tag, val);
@@ -305,7 +304,7 @@ public:
size_t numValues() const override {
size_t nVals = 0;
size_t bufIdx = 0;
- while (bufIdx < _tagList.size()) {
+ while (bufIdx < _numValues) {
auto tag = _tagList[bufIdx];
auto val = _valList[bufIdx];
if (tag == TypeTags::Boolean && !bitcastTo<bool>(val)) {
@@ -324,10 +323,7 @@ public:
auto bufferLen = _valueBufferBuilder->len();
size_t bufIdx = 0;
size_t rowIdx = 0;
- // The 'row' output parameter might be smaller than the number of values owned by this
- // builder. Be careful to only read as many values into 'row' as this output 'row' has space
- // for.
- while (rowIdx < row.size()) {
+ while (bufIdx < _numValues) {
invariant(rowIdx < row.size());
auto [_, tagNothing, valNothing] = getValue(bufIdx++, bufferLen);
tassert(6136200, "sbe tag must be 'Boolean'", tagNothing == TypeTags::Boolean);
diff --git a/src/mongo/db/exec/sbe/values/value_serialization_test.cpp b/src/mongo/db/exec/sbe/values/value_serialization_test.cpp
index ceacf610a4a..5b44bef6549 100644
--- a/src/mongo/db/exec/sbe/values/value_serialization_test.cpp
+++ b/src/mongo/db/exec/sbe/values/value_serialization_test.cpp
@@ -268,18 +268,6 @@ TEST_F(ValueSerializeForKeyString, SbeArray) {
runTest({{testDataTag, testDataVal}});
}
-TEST_F(ValueSerializeForKeyString, ArraySet) {
- auto [tag, val] = sbe::value::makeNewArraySet();
- sbe::value::ValueGuard guard{tag, val};
- auto* arraySet = sbe::value::getArraySetView(val);
-
- arraySet->push_back(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(1));
- arraySet->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2));
- arraySet->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(3.0));
-
- runTest({{tag, val}});
-}
-
TEST_F(ValueSerializeForKeyString, DateTime) {
runTest({{value::TypeTags::Date, value::bitcastFrom<int64_t>(1234)},
{value::TypeTags::Timestamp, value::bitcastFrom<uint64_t>(5678)}});
@@ -454,30 +442,4 @@ TEST_F(ValueSerializeForKeyString, BsonCodeWScope) {
runTest({{cwsTag1, cwsVal1}, {cwsTag2, cwsVal2}, {cwsTag3, cwsVal3}});
}
-
-// Test that roundtripping through KeyString works for a wide row. KeyStrings used in indexes are
-// typically constrained in the number of components they can have, since we limit compound indexes
-// to at most 32 components. But roundtripping rows wider than 32 still needs to work.
-//
-// This test was originally designed to reproduce SERVER-76321.
-TEST_F(ValueSerializeForKeyString, RoundtripWideRow) {
- std::vector<std::pair<sbe::value::TypeTags, sbe::value::Value>> row;
- for (int32_t i = 0; i < 40; ++i) {
- row.emplace_back(sbe::value::TypeTags::NumberInt32, sbe::value::bitcastFrom<int32_t>(i));
- }
- runTest(row);
-}
-
-// Test that roundtripping through KeyString works for ObjectIdType: ObjectId; bsonObjectId.
-TEST_F(ValueSerializeForKeyString, RoundtripObjectIdType) {
- auto [objectIdTag, objectIdVal] = value::makeNewObjectId();
-
- auto oid = OID::gen();
- auto obj = BSON("" << oid);
- auto oidStorage = obj.firstElement().value();
-
- sbe::value::ValueGuard testDataGuard{objectIdTag, objectIdVal};
- runTest({{objectIdTag, objectIdVal},
- {value::TypeTags::bsonObjectId, value::bitcastFrom<const char*>(oidStorage)}});
-}
} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/vm/arith.cpp b/src/mongo/db/exec/sbe/vm/arith.cpp
index e6c9d380ffd..e4c67775ad8 100644
--- a/src/mongo/db/exec/sbe/vm/arith.cpp
+++ b/src/mongo/db/exec/sbe/vm/arith.cpp
@@ -503,57 +503,6 @@ void ByteCode::aggDoubleDoubleSumImpl(value::Array* arr,
}
}
-void ByteCode::aggMergeDoubleDoubleSumsImpl(value::Array* accumulator,
- value::TypeTags rhsTag,
- value::Value rhsValue) {
- auto [accumWidestType, _1] = accumulator->getAt(AggSumValueElems::kNonDecimalTotalTag);
-
- tassert(7039532, "value must be of type 'Array'", rhsTag == value::TypeTags::Array);
- auto nextDoubleDoubleArr = value::getArrayView(rhsValue);
-
- tassert(7039533,
- "array does not have enough elements",
- nextDoubleDoubleArr->size() >= AggSumValueElems::kMaxSizeOfArray - 1);
-
- // First aggregate the non-decimal sum, then the non-decimal addend. Both should be doubles.
- auto [sumTag, sum] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalSum);
- tassert(7039534, "expected 'NumberDouble'", sumTag == value::TypeTags::NumberDouble);
- aggDoubleDoubleSumImpl(accumulator, sumTag, sum);
-
- auto [addendTag, addend] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalAddend);
- tassert(7039535, "expected 'NumberDouble'", addendTag == value::TypeTags::NumberDouble);
- // There is a special case when the 'sum' is infinite and the 'addend' is NaN. This DoubleDouble
- // value represents infinity, not NaN. Therefore, we avoid incorporating the NaN 'addend' value
- // into the sum.
- if (std::isfinite(value::bitcastTo<double>(sum)) ||
- !std::isnan(value::bitcastTo<double>(addend))) {
- aggDoubleDoubleSumImpl(accumulator, addendTag, addend);
- }
-
- // Determine the widest non-decimal type that we've seen so far, and set the accumulator state
- // accordingly. We do this after computing the sums, since 'aggDoubleDoubleSumImpl()' will
- // set the widest type to 'NumberDouble' when we call it above.
- auto [newValWidestType, _2] = nextDoubleDoubleArr->getAt(AggSumValueElems::kNonDecimalTotalTag);
- tassert(
- 7039536, "unexpected 'NumberDecimal'", newValWidestType != value::TypeTags::NumberDecimal);
- tassert(
- 7039537, "unexpected 'NumberDecimal'", accumWidestType != value::TypeTags::NumberDecimal);
- auto widestType = getWidestNumericalType(newValWidestType, accumWidestType);
- accumulator->setAt(
- AggSumValueElems::kNonDecimalTotalTag, widestType, value::bitcastFrom<int32_t>(0));
-
- // If there's a decimal128 sum as part of the incoming DoubleDouble sum, incorporate it into the
- // accumulator.
- if (nextDoubleDoubleArr->size() == AggSumValueElems::kMaxSizeOfArray) {
- auto [decimalTotalTag, decimalTotalVal] =
- nextDoubleDoubleArr->getAt(AggSumValueElems::kDecimalTotal);
- tassert(7039538,
- "The decimalTotal must be 'NumberDecimal'",
- decimalTotalTag == TypeTags::NumberDecimal);
- aggDoubleDoubleSumImpl(accumulator, decimalTotalTag, decimalTotalVal);
- }
-}
-
void ByteCode::aggStdDevImpl(value::Array* arr, value::TypeTags rhsTag, value::Value rhsValue) {
if (!isNumber(rhsTag)) {
return;
@@ -602,67 +551,6 @@ void ByteCode::aggStdDevImpl(value::Array* arr, value::TypeTags rhsTag, value::V
return setStdDevArray(newCountVal, newMeanVal, newM2Val, arr);
}
-void ByteCode::aggMergeStdDevsImpl(value::Array* accumulator,
- value::TypeTags rhsTag,
- value::Value rhsValue) {
- tassert(7039542, "expected value of type 'Array'", rhsTag == value::TypeTags::Array);
- auto nextArr = value::getArrayView(rhsValue);
-
- tassert(7039543,
- "expected array to have exactly 3 elements",
- accumulator->size() == AggStdDevValueElems::kSizeOfArray);
- tassert(7039544,
- "expected array to have exactly 3 elements",
- nextArr->size() == AggStdDevValueElems::kSizeOfArray);
-
- auto [newCountTag, newCountVal] = nextArr->getAt(AggStdDevValueElems::kCount);
- tassert(7039545, "expected 64-bit int", newCountTag == value::TypeTags::NumberInt64);
- int64_t newCount = value::bitcastTo<int64_t>(newCountVal);
-
- // If the incoming partial aggregate has a count of zero, then it represents the partial
- // standard deviation of no data points. This means that it can be safely ignored, and we return
- // the accumulator as is.
- if (newCount == 0) {
- return;
- }
-
- auto [oldCountTag, oldCountVal] = accumulator->getAt(AggStdDevValueElems::kCount);
- tassert(7039546, "expected 64-bit int", oldCountTag == value::TypeTags::NumberInt64);
- int64_t oldCount = value::bitcastTo<int64_t>(oldCountVal);
-
- auto [oldMeanTag, oldMeanVal] = accumulator->getAt(AggStdDevValueElems::kRunningMean);
- tassert(7039547, "expected double", oldMeanTag == value::TypeTags::NumberDouble);
- double oldMean = value::bitcastTo<double>(oldMeanVal);
-
- auto [newMeanTag, newMeanVal] = nextArr->getAt(AggStdDevValueElems::kRunningMean);
- tassert(7039548, "expected double", newMeanTag == value::TypeTags::NumberDouble);
- double newMean = value::bitcastTo<double>(newMeanVal);
-
- auto [oldM2Tag, oldM2Val] = accumulator->getAt(AggStdDevValueElems::kRunningM2);
- tassert(7039531, "expected double", oldM2Tag == value::TypeTags::NumberDouble);
- double oldM2 = value::bitcastTo<double>(oldM2Val);
-
- auto [newM2Tag, newM2Val] = nextArr->getAt(AggStdDevValueElems::kRunningM2);
- tassert(7039541, "expected double", newM2Tag == value::TypeTags::NumberDouble);
- double newM2 = value::bitcastTo<double>(newM2Val);
-
- const double delta = newMean - oldMean;
- // We've already handled the case where 'newCount' is zero above. This means that 'totalCount'
- // must be positive, and prevents us from ever dividing by zero in the subsequent calculation.
- int64_t totalCount = oldCount + newCount;
- if (delta != 0) {
- newMean = ((oldCount * oldMean) + (newCount * newMean)) / totalCount;
- newM2 += delta * delta *
- (static_cast<double>(oldCount) * static_cast<double>(newCount) / totalCount);
- }
- newM2 += oldM2;
-
- setStdDevArray(value::bitcastFrom<int64_t>(totalCount),
- value::bitcastFrom<double>(newMean),
- value::bitcastFrom<double>(newM2),
- accumulator);
-}
-
std::tuple<bool, value::TypeTags, value::Value> ByteCode::aggStdDevFinalizeImpl(
value::Value fieldValue, bool isSamp) {
auto arr = value::getArrayView(fieldValue);
@@ -1027,7 +915,7 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericLn(value::TypeT
if (!operand.isGreater(Decimal128::kNormalizedZero) && !operand.isNaN()) {
return {false, value::TypeTags::Nothing, 0};
}
- auto operandLn = operand.naturalLogarithm();
+ auto operandLn = operand.logarithm();
auto [tag, value] = value::makeCopyDecimal(operandLn);
return {true, tag, value};
diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp
index 812de5232af..7eb8eb7149e 100644
--- a/src/mongo/db/exec/sbe/vm/vm.cpp
+++ b/src/mongo/db/exec/sbe/vm/vm.cpp
@@ -1072,40 +1072,35 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::aggSum(value::TypeTags
return genericAdd(accTag, accValue, fieldTag, fieldValue);
}
-template <bool merging>
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggDoubleDoubleSum(
ArityType arity) {
+
auto [_, fieldTag, fieldValue] = getFromStack(1);
// Move the incoming accumulator state from the stack. Given that we are now the owner of the
// state we are free to do any in-place update as we see fit.
auto [accTag, accValue] = moveOwnedFromStack(0);
+ value::ValueGuard guard{accTag, accValue};
// Initialize the accumulator.
if (accTag == value::TypeTags::Nothing) {
std::tie(accTag, accValue) = value::makeNewArray();
- value::ValueGuard newArrGuard{accTag, accValue};
+ value::ValueGuard guard{accTag, accValue};
auto arr = value::getArrayView(accValue);
arr->reserve(AggSumValueElems::kMaxSizeOfArray);
- // The order of the following three elements should match to 'AggSumValueElems'. An absent
- // 'kDecimalTotal' element means that we've not seen any decimal value. So, we're not adding
- // 'kDecimalTotal' element yet.
+ // The order of the following three elements should match to 'AggSumValueElems'.
arr->push_back(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(0));
arr->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(0.0));
arr->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(0.0));
- newArrGuard.reset();
+ // The absent 'kDecimalTotal' element means that we've not seen any decimal value. So, we're
+ // not adding 'kDecimalTotal' element yet.
+ aggDoubleDoubleSumImpl(arr, fieldTag, fieldValue);
+ guard.reset();
+ return {true, accTag, accValue};
}
-
- value::ValueGuard guard{accTag, accValue};
tassert(5755317, "The result slot must be Array-typed", accTag == value::TypeTags::Array);
- auto accumulator = value::getArrayView(accValue);
-
- if constexpr (merging) {
- aggMergeDoubleDoubleSumsImpl(accumulator, fieldTag, fieldValue);
- } else {
- aggDoubleDoubleSumImpl(accumulator, fieldTag, fieldValue);
- }
+ aggDoubleDoubleSumImpl(value::getArrayView(accValue), fieldTag, fieldValue);
guard.reset();
return {true, accTag, accValue};
}
@@ -1240,37 +1235,31 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDoubleDoublePar
return {true, tag, val};
}
-template <bool merging>
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggStdDev(ArityType arity) {
auto [_, fieldTag, fieldValue] = getFromStack(1);
// Move the incoming accumulator state from the stack. Given that we are now the owner of the
// state we are free to do any in-place update as we see fit.
auto [accTag, accValue] = moveOwnedFromStack(0);
+ value::ValueGuard guard{accTag, accValue};
// Initialize the accumulator.
if (accTag == value::TypeTags::Nothing) {
- std::tie(accTag, accValue) = value::makeNewArray();
- value::ValueGuard newArrGuard{accTag, accValue};
- auto arr = value::getArrayView(accValue);
+ auto [newAccTag, newAccValue] = value::makeNewArray();
+ value::ValueGuard newGuard{newAccTag, newAccValue};
+ auto arr = value::getArrayView(newAccValue);
arr->reserve(AggStdDevValueElems::kSizeOfArray);
// The order of the following three elements should match to 'AggStdDevValueElems'.
arr->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(0));
arr->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(0.0));
arr->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(0.0));
- newArrGuard.reset();
+ aggStdDevImpl(arr, fieldTag, fieldValue);
+ newGuard.reset();
+ return {true, newAccTag, newAccValue};
}
-
- value::ValueGuard guard{accTag, accValue};
tassert(5755210, "The result slot must be Array-typed", accTag == value::TypeTags::Array);
- auto accumulator = value::getArrayView(accValue);
-
- if constexpr (merging) {
- aggMergeStdDevsImpl(accumulator, fieldTag, fieldValue);
- } else {
- aggStdDevImpl(accumulator, fieldTag, fieldValue);
- }
+ aggStdDevImpl(value::getArrayView(accValue), fieldTag, fieldValue);
guard.reset();
return {true, accTag, accValue};
}
@@ -3030,120 +3019,6 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinConcat(ArityTyp
return {true, strTag, strValue};
}
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinConcatArrays(ArityType arity) {
- auto [resTag, resVal] = value::makeNewArray();
- value::ValueGuard resGuard{resTag, resVal};
- auto resView = value::getArrayView(resVal);
-
- for (ArityType idx = 0; idx < arity; ++idx) {
- auto [_, tag, val] = getFromStack(idx);
- if (!value::isArray(tag)) {
- return {false, value::TypeTags::Nothing, 0};
- }
-
- for (auto ae = value::ArrayEnumerator{tag, val}; !ae.atEnd(); ae.advance()) {
- auto [elTag, elVal] = ae.getViewOfValue();
- auto [copyTag, copyVal] = value::copyValue(elTag, elVal);
- resView->push_back(copyTag, copyVal);
- }
- }
-
- resGuard.reset();
-
- return {true, resTag, resVal};
-}
-
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggConcatArraysCapped(
- ArityType arity) {
- auto [ownArr, tagArr, valArr] = getFromStack(0);
- auto [tagNewElem, valNewElem] = moveOwnedFromStack(1);
- value::ValueGuard guardNewElem{tagNewElem, valNewElem};
- auto [_, tagSizeCap, valSizeCap] = getFromStack(2);
-
- tassert(7039508,
- "'cap' parameter must be a 32-bit int",
- tagSizeCap == value::TypeTags::NumberInt32);
- const int32_t sizeCap = value::bitcastTo<int32_t>(valSizeCap);
-
- // We expect the new value we are adding to the accumulator to be a two-element array where
- // the first element is the array to concatenate and the second value is the corresponding size.
- tassert(7039512, "expected value of type 'Array'", tagNewElem == value::TypeTags::Array);
- auto newArr = value::getArrayView(valNewElem);
- tassert(7039527,
- "array had unexpected size",
- newArr->size() == static_cast<size_t>(AggArrayWithSize::kLast));
-
- // Create a new array to hold size and added elements, if is it does not exist yet.
- if (tagArr == value::TypeTags::Nothing) {
- ownArr = true;
- std::tie(tagArr, valArr) = value::makeNewArray();
- auto arr = value::getArrayView(valArr);
-
- auto [tagAccArr, valAccArr] = value::makeNewArray();
-
- // The order is important! The accumulated array should be at index
- // AggArrayWithSize::kValues, and the size should be at index
- // AggArrayWithSize::kSizeOfValues.
- arr->push_back(tagAccArr, valAccArr);
- arr->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(0));
- } else {
- // Take ownership of the accumulator.
- topStack(false, value::TypeTags::Nothing, 0);
- }
-
- tassert(7039513, "expected array to be owned", ownArr);
- value::ValueGuard accumulatorGuard{tagArr, valArr};
- tassert(7039514, "expected accumulator to have type 'Array'", tagArr == value::TypeTags::Array);
- auto arr = value::getArrayView(valArr);
- tassert(7039515,
- "accumulator was array of unexpected size",
- arr->size() == static_cast<size_t>(AggArrayWithSize::kLast));
-
- // Check that the accumulated size after concatentation won't exceed the limit.
- {
- auto [tagAccSize, valAccSize] =
- arr->getAt(static_cast<size_t>(AggArrayWithSize::kSizeOfValues));
- auto [tagNewSize, valNewSize] =
- newArr->getAt(static_cast<size_t>(AggArrayWithSize::kSizeOfValues));
- tassert(7039516, "expected 64-bit int", tagAccSize == value::TypeTags::NumberInt64);
- tassert(7039517, "expected 64-bit int", tagNewSize == value::TypeTags::NumberInt64);
- const int64_t currentSize = value::bitcastTo<int64_t>(valAccSize);
- const int64_t newSize = value::bitcastTo<int64_t>(valNewSize);
- const int64_t totalSize = currentSize + newSize;
-
- if (totalSize >= static_cast<int64_t>(sizeCap)) {
- uasserted(ErrorCodes::ExceededMemoryLimit,
- str::stream() << "Used too much memory for a single array. Memory limit: "
- << sizeCap << ". Concatentating array of " << arr->size()
- << " elements and " << currentSize << " bytes with array of "
- << newArr->size() << " elements and " << newSize << " bytes.");
- }
-
- // We are still under the size limit. Set the new total size in the accumulator.
- arr->setAt(static_cast<size_t>(AggArrayWithSize::kSizeOfValues),
- value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(totalSize));
- }
-
- auto [tagAccArr, valAccArr] = arr->getAt(static_cast<size_t>(AggArrayWithSize::kValues));
- tassert(7039518, "expected value of type 'Array'", tagAccArr == value::TypeTags::Array);
- auto accArr = value::getArrayView(valAccArr);
-
- auto [tagNewArray, valNewArray] = newArr->getAt(static_cast<size_t>(AggArrayWithSize::kValues));
- tassert(7039519, "expected value of type 'Array'", tagNewArray == value::TypeTags::Array);
-
- for (auto i = value::ArrayEnumerator{tagNewArray, valNewArray}; !i.atEnd(); i.advance()) {
- auto [elTag, elVal] = i.getViewOfValue();
- // TODO SERVER-71952: Since 'valNewArray' is owned here, in the future we could avoid this
- // copy by moving the element out of the array.
- auto [copyTag, copyVal] = value::copyValue(elTag, elVal);
- accArr->push_back(copyTag, copyVal);
- }
-
- accumulatorGuard.reset();
- return {ownArr, tagArr, valArr};
-}
-
std::pair<value::TypeTags, value::Value> ByteCode::genericIsMember(value::TypeTags lhsTag,
value::Value lhsVal,
value::TypeTags rhsTag,
@@ -3521,166 +3396,6 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinSetUnion(ArityT
return setUnion(argTags, argVals);
}
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::aggSetUnionCappedImpl(
- value::TypeTags tagNewElem,
- value::Value valNewElem,
- int32_t sizeCap,
- CollatorInterface* collator) {
- value::ValueGuard guardNewElem{tagNewElem, valNewElem};
- auto [ownAcc, tagAcc, valAcc] = getFromStack(0);
-
- // We expect the new value we are adding to the accumulator to be a two-element array where
- // the first element is the new set of values and the second value is the corresponding size.
- tassert(7039526, "expected value of type 'Array'", tagNewElem == value::TypeTags::Array);
- auto newArr = value::getArrayView(valNewElem);
- tassert(7039528,
- "array had unexpected size",
- newArr->size() == static_cast<size_t>(AggArrayWithSize::kLast));
-
- // Create a new array is it does not exist yet.
- if (tagAcc == value::TypeTags::Nothing) {
- ownAcc = true;
- std::tie(tagAcc, valAcc) = value::makeNewArray();
- auto accArray = value::getArrayView(valAcc);
-
- auto [tagAccSet, valAccSet] = value::makeNewArraySet(collator);
-
- // The order is important! The accumulated array should be at index
- // AggArrayWithSize::kValues, and the size should be at index
- // AggArrayWithSize::kSizeOfValues.
- accArray->push_back(tagAccSet, valAccSet);
- accArray->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(0));
- } else {
- // Take ownership of the accumulator.
- topStack(false, value::TypeTags::Nothing, 0);
- }
-
- tassert(7039520, "expected accumulator value to be owned", ownAcc);
- value::ValueGuard guardArr{tagAcc, valAcc};
-
- tassert(
- 7039521, "expected accumulator to be of type 'Array'", tagAcc == value::TypeTags::Array);
- auto accArray = value::getArrayView(valAcc);
- tassert(7039522,
- "array had unexpected size",
- accArray->size() == static_cast<size_t>(AggArrayWithSize::kLast));
-
- auto [tagAccArrSet, valAccArrSet] =
- accArray->getAt(static_cast<size_t>(AggArrayWithSize::kValues));
- tassert(
- 7039523, "expected value of type 'ArraySet'", tagAccArrSet == value::TypeTags::ArraySet);
- auto accArrSet = value::getArraySetView(valAccArrSet);
-
- // Extract the current size of the accumulator. As we add elements to the set, we will increment
- // the current size accordingly and throw an exception if we ever exceed the size limit. We
- // cannot simply sum the two sizes, since the two sets could have a substantial intersection.
- auto [tagAccSize, valAccSize] =
- accArray->getAt(static_cast<size_t>(AggArrayWithSize::kSizeOfValues));
- tassert(7039524, "expected 64-bit int", tagAccSize == value::TypeTags::NumberInt64);
- int64_t currentSize = value::bitcastTo<int64_t>(valAccSize);
-
- auto [tagNewValSet, valNewValSet] =
- newArr->getAt(static_cast<size_t>(AggArrayWithSize::kValues));
- tassert(
- 7039525, "expected value of type 'ArraySet'", tagNewValSet == value::TypeTags::ArraySet);
-
- for (auto i = value::ArrayEnumerator{tagNewValSet, valNewValSet}; !i.atEnd(); i.advance()) {
- auto [elTag, elVal] = i.getViewOfValue();
- int elemSize = value::getApproximateSize(elTag, elVal);
- // TODO SERVER-71952: Since 'valNewValSet' is owned here, in the future we could avoid this
- // copy by moving the element out of the array.
- auto [copyTag, copyVal] = value::copyValue(elTag, elVal);
- bool inserted = accArrSet->push_back(copyTag, copyVal);
-
- if (inserted) {
- currentSize += elemSize;
- if (currentSize >= static_cast<int64_t>(sizeCap)) {
- uasserted(ErrorCodes::ExceededMemoryLimit,
- str::stream() << "Used too much memory for a single array. Memory limit: "
- << sizeCap << ". Current set has " << accArrSet->size()
- << " elements and is " << currentSize << " bytes.");
- }
- }
- }
-
- // Update the accumulator with the new total size.
- accArray->setAt(static_cast<size_t>(AggArrayWithSize::kSizeOfValues),
- value::TypeTags::NumberInt64,
- value::bitcastFrom<int64_t>(currentSize));
-
- guardArr.reset();
- return {ownAcc, tagAcc, valAcc};
-}
-
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggSetUnion(ArityType arity) {
- auto [ownAcc, tagAcc, valAcc] = getFromStack(0);
-
- if (tagAcc == value::TypeTags::Nothing) {
- // Initialize the accumulator.
- ownAcc = true;
- std::tie(tagAcc, valAcc) = value::makeNewArraySet();
- } else {
- // Take ownership of the accumulator.
- topStack(false, value::TypeTags::Nothing, 0);
- }
-
- tassert(7039552, "accumulator must be owned", ownAcc);
- value::ValueGuard guardAcc{tagAcc, valAcc};
- tassert(7039553, "accumulator must be of type ArraySet", tagAcc == value::TypeTags::ArraySet);
- auto acc = value::getArraySetView(valAcc);
-
- auto [tagNewSet, valNewSet] = moveOwnedFromStack(1);
- value::ValueGuard guardNewSet{tagNewSet, valNewSet};
- if (!value::isArray(tagNewSet)) {
- return {false, value::TypeTags::Nothing, 0};
- }
-
- auto i = value::ArrayEnumerator{tagNewSet, valNewSet};
- while (!i.atEnd()) {
- auto [elTag, elVal] = i.getViewOfValue();
- auto [copyTag, copyVal] = value::copyValue(elTag, elVal);
- acc->push_back(copyTag, copyVal);
- i.advance();
- }
-
- guardAcc.reset();
- return {ownAcc, tagAcc, valAcc};
-}
-
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggSetUnionCapped(
- ArityType arity) {
- auto [tagNewElem, valNewElem] = moveOwnedFromStack(1);
- value::ValueGuard guardNewElem{tagNewElem, valNewElem};
-
- auto [_, tagSizeCap, valSizeCap] = getFromStack(2);
- tassert(7039509,
- "'cap' parameter must be a 32-bit int",
- tagSizeCap == value::TypeTags::NumberInt32);
- const size_t sizeCap = value::bitcastTo<int32_t>(valSizeCap);
-
- guardNewElem.reset();
- return aggSetUnionCappedImpl(tagNewElem, valNewElem, sizeCap, nullptr /*collator*/);
-}
-
-std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinAggCollSetUnionCapped(
- ArityType arity) {
- auto [_1, tagColl, valColl] = getFromStack(1);
- auto [tagNewElem, valNewElem] = moveOwnedFromStack(2);
- value::ValueGuard guardNewElem{tagNewElem, valNewElem};
- auto [_2, tagSizeCap, valSizeCap] = getFromStack(3);
-
- tassert(7039510, "expected value of type 'collator'", tagColl == value::TypeTags::collator);
- tassert(7039511,
- "'cap' parameter must be a 32-bit int",
- tagSizeCap == value::TypeTags::NumberInt32);
-
- guardNewElem.reset();
- return aggSetUnionCappedImpl(tagNewElem,
- valNewElem,
- value::bitcastTo<int32_t>(valSizeCap),
- value::getCollatorView(valColl));
-}
-
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinCollSetIntersection(
ArityType arity) {
invariant(arity >= 1);
@@ -4667,7 +4382,7 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti
case Builtin::doubleDoubleSum:
return builtinDoubleDoubleSum(arity);
case Builtin::aggDoubleDoubleSum:
- return builtinAggDoubleDoubleSum<false /*merging*/>(arity);
+ return builtinAggDoubleDoubleSum(arity);
case Builtin::doubleDoubleSumFinalize:
return builtinDoubleDoubleSumFinalize<>(arity);
case Builtin::doubleDoubleMergeSumFinalize:
@@ -4676,12 +4391,8 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti
return builtinDoubleDoubleSumFinalize<true /*keepIntegerPrecision*/>(arity);
case Builtin::doubleDoublePartialSumFinalize:
return builtinDoubleDoublePartialSumFinalize(arity);
- case Builtin::aggMergeDoubleDoubleSums:
- return builtinAggDoubleDoubleSum<true /*merging*/>(arity);
case Builtin::aggStdDev:
- return builtinAggStdDev<false /*merging*/>(arity);
- case Builtin::aggMergeStdDevs:
- return builtinAggStdDev<true /*merging*/>(arity);
+ return builtinAggStdDev(arity);
case Builtin::stdDevPopFinalize:
return builtinStdDevPopFinalize(arity);
case Builtin::stdDevSampFinalize:
@@ -4734,16 +4445,6 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti
return builtinRound(arity);
case Builtin::concat:
return builtinConcat(arity);
- case Builtin::concatArrays:
- return builtinConcatArrays(arity);
- case Builtin::aggConcatArraysCapped:
- return builtinAggConcatArraysCapped(arity);
- case Builtin::aggSetUnion:
- return builtinAggSetUnion(arity);
- case Builtin::aggSetUnionCapped:
- return builtinAggSetUnionCapped(arity);
- case Builtin::aggCollSetUnionCapped:
- return builtinAggCollSetUnionCapped(arity);
case Builtin::isMember:
return builtinIsMember(arity);
case Builtin::collIsMember:
diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h
index e5148e8d4b2..255a1a497c2 100644
--- a/src/mongo/db/exec/sbe/vm/vm.h
+++ b/src/mongo/db/exec/sbe/vm/vm.h
@@ -511,33 +511,12 @@ enum class Builtin : uint8_t {
collAddToSet, // agg function to append to a set (with collation)
collAddToSetCapped, // agg function to append to a set (with collation), fails when the set
// reaches specified size
-
- // Special double summation.
- doubleDoubleSum,
- // A variant of the standard sum aggregate function which maintains a DoubleDouble as the
- // accumulator's underlying state.
+ doubleDoubleSum, // special double summation
aggDoubleDoubleSum,
- // Converts a DoubleDouble sum into a single numeric scalar for use once the summation is
- // complete.
doubleDoubleSumFinalize,
- // A form of doubleDoubleSum finalization only necessary for sharding support when the cluster
- // is not yet fully upgraded to FCV 6.0.
doubleDoubleMergeSumFinalize,
- // Converts a partial sum into a format suitable for serialization over the wire to the merging
- // node. The merging node expects the internal state of the DoubleDouble summation to be
- // serialized in a particular format.
doubleDoublePartialSumFinalize,
- // An agg function which can be used to sum a sequence of DoubleDouble inputs, producing the
- // resulting total as a DoubleDouble.
- aggMergeDoubleDoubleSums,
-
- // Implements Welford's online algorithm for computing sample or population standard deviation
- // in a single pass.
aggStdDev,
- // Combines standard deviations that have been partially computed on a subset of the data
- // using Welford's online algorithm.
- aggMergeStdDevs,
-
stdDevPopFinalize,
stdDevSampFinalize,
bitTestZero, // test bitwise mask & value is zero
@@ -548,18 +527,6 @@ enum class Builtin : uint8_t {
toLower,
coerceToString,
concat,
- concatArrays,
-
- // Agg function to concatenate arrays, failing when the accumulator reaches a specified size.
- aggConcatArraysCapped,
-
- // Agg functions to compute the set union of two arrays, failing when the accumulator reaches a
- // specified size.
- aggSetUnionCapped,
- aggCollSetUnionCapped,
- // Agg function for a simple set union (with no size cap or collation).
- aggSetUnion,
-
acos,
acosh,
asin,
@@ -1013,19 +980,11 @@ private:
value::TypeTags fieldTag,
value::Value fieldValue);
- void aggDoubleDoubleSumImpl(value::Array* accumulator,
- value::TypeTags rhsTag,
- value::Value rhsValue);
- void aggMergeDoubleDoubleSumsImpl(value::Array* accumulator,
- value::TypeTags rhsTag,
- value::Value rhsValue);
+ void aggDoubleDoubleSumImpl(value::Array* arr, value::TypeTags rhsTag, value::Value rhsValue);
// This is an implementation of the following algorithm:
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
- void aggStdDevImpl(value::Array* accumulator, value::TypeTags rhsTag, value::Value rhsValue);
- void aggMergeStdDevsImpl(value::Array* accumulator,
- value::TypeTags rhsTag,
- value::Value rhsValue);
+ void aggStdDevImpl(value::Array* arr, value::TypeTags rhsTag, value::Value rhsValue);
std::tuple<bool, value::TypeTags, value::Value> aggStdDevFinalizeImpl(value::Value fieldValue,
bool isSamp);
@@ -1144,24 +1103,14 @@ private:
CollatorInterface* collator);
std::tuple<bool, value::TypeTags, value::Value> builtinAddToSetCapped(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinCollAddToSetCapped(ArityType arity);
-
std::tuple<bool, value::TypeTags, value::Value> builtinDoubleDoubleSum(ArityType arity);
- // The template parameter is false for a regular DoubleDouble summation and true if merging
- // partially computed DoubleDouble sums.
- template <bool merging>
std::tuple<bool, value::TypeTags, value::Value> builtinAggDoubleDoubleSum(ArityType arity);
-
// This is only for compatibility with mongos/sharding and we will revisit this later.
template <bool keepIntegerPrecision = false>
std::tuple<bool, value::TypeTags, value::Value> builtinDoubleDoubleSumFinalize(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinDoubleDoublePartialSumFinalize(
ArityType arity);
-
- // The template parameter is false for a regular std dev and true if merging partially computed
- // standard devations.
- template <bool merging>
std::tuple<bool, value::TypeTags, value::Value> builtinAggStdDev(ArityType arity);
-
std::tuple<bool, value::TypeTags, value::Value> builtinStdDevPopFinalize(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinStdDevSampFinalize(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinBitTestZero(ArityType arity);
@@ -1188,16 +1137,6 @@ private:
std::tuple<bool, value::TypeTags, value::Value> builtinTanh(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinRound(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinConcat(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> builtinConcatArrays(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> builtinAggConcatArraysCapped(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> builtinAggSetUnion(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> builtinAggSetUnionCapped(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> builtinAggCollSetUnionCapped(ArityType arity);
- std::tuple<bool, value::TypeTags, value::Value> aggSetUnionCappedImpl(
- value::TypeTags tagNewElem,
- value::Value valNewElem,
- int32_t sizeCap,
- CollatorInterface* collator);
std::tuple<bool, value::TypeTags, value::Value> builtinIsMember(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinCollIsMember(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIndexOfBytes(ArityType arity);
diff --git a/src/mongo/db/exec/scoped_timer.cpp b/src/mongo/db/exec/scoped_timer.cpp
index bd53003faa5..f33c498a57a 100644
--- a/src/mongo/db/exec/scoped_timer.cpp
+++ b/src/mongo/db/exec/scoped_timer.cpp
@@ -32,8 +32,6 @@
#include "mongo/db/exec/scoped_timer.h"
#include "mongo/util/clock_source.h"
-#include "mongo/bson/bsonobjbuilder.h"
-
namespace mongo {
ScopedTimer::ScopedTimer(ClockSource* cs, long long* counter)
@@ -44,25 +42,4 @@ ScopedTimer::~ScopedTimer() {
*_counter += elapsed;
}
-
-TimeElapsedBuilderScopedTimer::TimeElapsedBuilderScopedTimer(ClockSource* clockSource,
- StringData description,
- BSONObjBuilder* builder)
- : _clockSource(clockSource),
- _description(description),
- _beginTime(clockSource->now()),
- _builder(builder) {}
-
-TimeElapsedBuilderScopedTimer::~TimeElapsedBuilderScopedTimer() {
- mongo::Milliseconds elapsedTime = _clockSource->now() - _beginTime;
- _builder->append(_description, elapsedTime.toString());
-}
-
-boost::optional<TimeElapsedBuilderScopedTimer> createTimeElapsedBuilderScopedTimer(
- ClockSource* clockSource, StringData description, BSONObjBuilder* builder) {
- if (builder == nullptr) {
- return boost::none;
- }
- return TimeElapsedBuilderScopedTimer(clockSource, description, builder);
-}
} // namespace mongo
diff --git a/src/mongo/db/exec/scoped_timer.h b/src/mongo/db/exec/scoped_timer.h
index c3c52243369..c11c424e59a 100644
--- a/src/mongo/db/exec/scoped_timer.h
+++ b/src/mongo/db/exec/scoped_timer.h
@@ -59,28 +59,4 @@ private:
const Date_t _start;
};
-/**
- * The timer appends the time elapsed since its construction to a BSON Object.
- */
-class TimeElapsedBuilderScopedTimer {
-public:
- explicit TimeElapsedBuilderScopedTimer(ClockSource* clockSource,
- StringData description,
- BSONObjBuilder* builder);
- ~TimeElapsedBuilderScopedTimer();
-
-private:
- ClockSource* _clockSource;
- StringData _description;
- Date_t _beginTime;
- BSONObjBuilder* _builder;
-};
-
-/*
- * This helper function only creates a TimeElapsedBuilderScopedTimer when a valid pointer to a
- * builder is passed in. This is used when timing startup tasks, so that tasks that run during
- * startup and outside of startup will only be timed when they are called during startup.
- */
-boost::optional<TimeElapsedBuilderScopedTimer> createTimeElapsedBuilderScopedTimer(
- ClockSource* clockSource, StringData description, BSONObjBuilder* builder);
} // namespace mongo
diff --git a/src/mongo/db/exec/skip.cpp b/src/mongo/db/exec/skip.cpp
index 755e9278fe0..d3d0fc48afd 100644
--- a/src/mongo/db/exec/skip.cpp
+++ b/src/mongo/db/exec/skip.cpp
@@ -47,7 +47,7 @@ SkipStage::SkipStage(ExpressionContext* expCtx,
long long toSkip,
WorkingSet* ws,
std::unique_ptr<PlanStage> child)
- : PlanStage(kStageType, expCtx), _ws(ws), _leftToSkip(toSkip), _skipAmount(toSkip) {
+ : PlanStage(kStageType, expCtx), _ws(ws), _toSkip(toSkip) {
_children.emplace_back(std::move(child));
}
@@ -63,9 +63,9 @@ PlanStage::StageState SkipStage::doWork(WorkingSetID* out) {
if (PlanStage::ADVANCED == status) {
// If we're still skipping results...
- if (_leftToSkip > 0) {
+ if (_toSkip > 0) {
// ...drop the result.
- --_leftToSkip;
+ --_toSkip;
_ws->free(id);
return PlanStage::NEED_TIME;
}
@@ -82,7 +82,7 @@ PlanStage::StageState SkipStage::doWork(WorkingSetID* out) {
unique_ptr<PlanStageStats> SkipStage::getStats() {
_commonStats.isEOF = isEOF();
- _specificStats.skip = _skipAmount;
+ _specificStats.skip = _toSkip;
unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_SKIP);
ret->specific = std::make_unique<SkipStats>(_specificStats);
ret->children.emplace_back(child()->getStats());
diff --git a/src/mongo/db/exec/skip.h b/src/mongo/db/exec/skip.h
index 5d0a764bd8a..24937662d02 100644
--- a/src/mongo/db/exec/skip.h
+++ b/src/mongo/db/exec/skip.h
@@ -66,13 +66,8 @@ public:
private:
WorkingSet* _ws;
- // The number of results left to skip. This number is decremented during query execution as we
- // successfully skip a document.
- long long _leftToSkip;
-
- // Represents the number of results to skip. Unlike '_leftToSkip', this remains constant and
- // is used when gathering statistics in explain.
- const long long _skipAmount;
+ // We drop the first _toSkip results that we would have returned.
+ long long _toSkip;
// Stats
SkipStats _specificStats;
diff --git a/src/mongo/db/exec/sort_executor.h b/src/mongo/db/exec/sort_executor.h
index 8120f52caa9..6509c767e9a 100644
--- a/src/mongo/db/exec/sort_executor.h
+++ b/src/mongo/db/exec/sort_executor.h
@@ -139,7 +139,7 @@ public:
}
_output.reset(_sorter->done());
_stats.keysSorted += _sorter->numSorted();
- _stats.spills += _sorter->stats().spilledRanges();
+ _stats.spills += _sorter->numSpills();
_stats.totalDataSizeBytes += _sorter->totalDataSizeSorted();
_sorter.reset();
}
diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp
index a4548ce4a4c..d83af04df07 100644
--- a/src/mongo/db/exec/stagedebug_cmd.cpp
+++ b/src/mongo/db/exec/stagedebug_cmd.cpp
@@ -261,7 +261,7 @@ public:
BSONObj keyPatternObj = keyPatternElement.Obj();
std::vector<const IndexDescriptor*> indexes;
collection->getIndexCatalog()->findIndexesByKeyPattern(
- opCtx, keyPatternObj, IndexCatalog::InclusionPolicy::kReady, &indexes);
+ opCtx, keyPatternObj, false, &indexes);
uassert(16890,
str::stream() << "Can't find index: " << keyPatternObj,
!indexes.empty());
diff --git a/src/mongo/db/exec/text_or.cpp b/src/mongo/db/exec/text_or.cpp
index f9e0745ca16..a0ba3eb347e 100644
--- a/src/mongo/db/exec/text_or.cpp
+++ b/src/mongo/db/exec/text_or.cpp
@@ -104,7 +104,9 @@ std::unique_ptr<PlanStageStats> TextOrStage::getStats() {
_commonStats.isEOF = isEOF();
if (_filter) {
- _commonStats.filter = _filter->serialize();
+ BSONObjBuilder bob;
+ _filter->serialize(&bob);
+ _commonStats.filter = bob.obj();
}
unique_ptr<PlanStageStats> ret = std::make_unique<PlanStageStats>(_commonStats, STAGE_TEXT_OR);
diff --git a/src/mongo/db/exec/update_stage.cpp b/src/mongo/db/exec/update_stage.cpp
index 32887ab1f19..5a273dc2d89 100644
--- a/src/mongo/db/exec/update_stage.cpp
+++ b/src/mongo/db/exec/update_stage.cpp
@@ -278,9 +278,6 @@ BSONObj UpdateStage::transformAndUpdate(const Snapshotted<BSONObj>& oldObj,
// Ensure we set the type correctly
args.source = writeToOrphan ? OperationSource::kFromMigrate : request->source();
- args.mustCheckExistenceForInsertOperations =
- driver->getUpdateExecutor()->getCheckExistenceForDiffInsertOperations();
-
if (inPlace) {
if (!request->explain()) {
newObj = oldObj.value();
@@ -462,41 +459,24 @@ PlanStage::StageState UpdateStage::doWork(WorkingSetID* out) {
bool writeToOrphan = false;
if (!_params.request->explain() && _isUserInitiatedWrite) {
- try {
- const auto action = _preWriteFilter.computeAction(member->doc.value());
- if (action == write_stage_common::PreWriteFilter::Action::kSkip) {
- LOGV2_DEBUG(
- 5983200,
- 3,
- "Skipping update operation to orphan document to prevent a wrong change "
- "stream event",
- "namespace"_attr = collection()->ns(),
- "record"_attr = member->doc.value());
- return PlanStage::NEED_TIME;
- } else if (action ==
- write_stage_common::PreWriteFilter::Action::kWriteAsFromMigrate) {
- LOGV2_DEBUG(
- 6184701,
- 3,
- "Marking update operation to orphan document with the fromMigrate flag "
- "to prevent a wrong change stream event",
- "namespace"_attr = collection()->ns(),
- "record"_attr = member->doc.value());
- writeToOrphan = true;
- }
- } catch (const ExceptionFor<ErrorCodes::StaleConfig>& ex) {
- if (ex->getVersionReceived() == ChunkVersion::IGNORED() &&
- ex->getCriticalSectionSignal()) {
- // If ChunkVersion is IGNORED and we encountered a critical section, then yield,
- // wait for critical section to finish and then we'll resume the write from the
- // point we had left. We do this to prevent large multi-writes from repeatedly
- // failing due to StaleConfig and exhausting the mongos retry attempts.
- planExecutorShardingCriticalSectionFuture(opCtx()) =
- ex->getCriticalSectionSignal();
- memberFreer.dismiss(); // Keep this member around so we can retry deleting it.
- return prepareToRetryWSM(id, out);
- }
- throw;
+ const auto action = _preWriteFilter.computeAction(member->doc.value());
+ if (action == write_stage_common::PreWriteFilter::Action::kSkip) {
+ LOGV2_DEBUG(
+ 5983200,
+ 3,
+ "Skipping update operation to orphan document to prevent a wrong change "
+ "stream event",
+ "namespace"_attr = collection()->ns(),
+ "record"_attr = member->doc.value());
+ return PlanStage::NEED_TIME;
+ } else if (action == write_stage_common::PreWriteFilter::Action::kWriteAsFromMigrate) {
+ LOGV2_DEBUG(6184701,
+ 3,
+ "Marking update operation to orphan document with the fromMigrate flag "
+ "to prevent a wrong change stream event",
+ "namespace"_attr = collection()->ns(),
+ "record"_attr = member->doc.value());
+ writeToOrphan = true;
}
}
@@ -526,18 +506,6 @@ PlanStage::StageState UpdateStage::doWork(WorkingSetID* out) {
} catch (const WriteConflictException&) {
memberFreer.dismiss(); // Keep this member around so we can retry updating it.
return prepareToRetryWSM(id, out);
- } catch (const ExceptionFor<ErrorCodes::StaleConfig>& ex) {
- if (ex->getVersionReceived() == ChunkVersion::IGNORED() &&
- ex->getCriticalSectionSignal()) {
- // If ChunkVersion is IGNORED and we encountered a critical section, then yield,
- // wait for critical section to finish and then we'll resume the write from the
- // point we had left. We do this to prevent large multi-writes from repeatedly
- // failing due to StaleConfig and exhausting the mongos retry attempts.
- planExecutorShardingCriticalSectionFuture(opCtx()) = ex->getCriticalSectionSignal();
- memberFreer.dismiss(); // Keep this member around so we can retry updating it.
- return prepareToRetryWSM(id, out);
- }
- throw;
}
// Set member's obj to be the doc we want to return.
diff --git a/src/mongo/db/exec/upsert_stage.cpp b/src/mongo/db/exec/upsert_stage.cpp
index dfb54a99082..423e300fcf7 100644
--- a/src/mongo/db/exec/upsert_stage.cpp
+++ b/src/mongo/db/exec/upsert_stage.cpp
@@ -31,7 +31,7 @@
#include "mongo/db/catalog/document_validation.h"
#include "mongo/db/catalog/local_oplog_info.h"
-#include "mongo/db/concurrency/exception_util.h"
+#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/curop_failpoint_helpers.h"
#include "mongo/db/query/query_feature_flags_gen.h"
#include "mongo/db/s/collection_sharding_state.h"
@@ -273,13 +273,8 @@ void UpsertStage::_generateNewDocumentFromSuppliedDoc(const FieldRefSet& immutab
UpdateDriver replacementDriver(nullptr);
// Create a new replacement-style update from the supplied document.
- replacementDriver.parse(
- write_ops::UpdateModification(
- suppliedDoc, write_ops::UpdateModification::ClassicTag{}, true /* isReplacement */),
- {});
+ replacementDriver.parse(write_ops::UpdateModification::parseFromClassicUpdate(suppliedDoc), {});
replacementDriver.setLogOp(false);
- replacementDriver.setBypassEmptyTsReplacement(
- static_cast<bool>(_params.request->getBypassEmptyTsReplacement()));
// We do not validate for storage, as we will validate the full document before inserting.
// However, we ensure that no immutable fields are modified.
diff --git a/src/mongo/db/exec/working_set_common.cpp b/src/mongo/db/exec/working_set_common.cpp
index 3920e6ad79a..1a1a04cade6 100644
--- a/src/mongo/db/exec/working_set_common.cpp
+++ b/src/mongo/db/exec/working_set_common.cpp
@@ -37,8 +37,6 @@
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/catalog/collection.h"
-#include "mongo/db/catalog/health_log_gen.h"
-#include "mongo/db/catalog/health_log_interface.h"
#include "mongo/db/exec/working_set.h"
#include "mongo/db/index/index_access_method.h"
#include "mongo/db/query/canonical_query.h"
@@ -46,7 +44,6 @@
#include "mongo/db/storage/execution_context.h"
#include "mongo/db/storage/index_entry_comparison.h"
#include "mongo/logv2/log.h"
-#include "mongo/util/stacktrace.h"
namespace mongo {
@@ -109,30 +106,6 @@ bool WorkingSetCommon::fetch(OperationContext* opCtx,
builder.append("pattern"_sd, ikd.indexKeyPattern);
return builder.obj();
};
-
- HealthLogEntry entry;
- entry.setNss(ns);
- entry.setTimestamp(Date_t::now());
- entry.setSeverity(SeverityEnum::Error);
- entry.setScope(ScopeEnum::Index);
- entry.setOperation("Index scan");
- entry.setMsg("Erroneous index key found with reference to non-existent record id");
-
- BSONObjBuilder bob;
- bob.append("recordId", member->recordId.toString());
-
- const BSONArray indexKeyData =
- logv2::seqLog(
- boost::make_transform_iterator(member->keyData.begin(), indexKeyEntryToObjFn),
- boost::make_transform_iterator(member->keyData.end(), indexKeyEntryToObjFn))
- .toBSONArray();
- bob.append("indexKeyData", indexKeyData);
-
- bob.appendElements(getStackTrace().getBSONRepresentation());
- entry.setData(bob.obj());
-
- HealthLogInterface::get(opCtx)->log(entry);
-
LOGV2_ERROR_OPTIONS(
4615603,
{logv2::UserAssertAfterLog(ErrorCodes::DataCorruptionDetected)},
@@ -141,7 +114,9 @@ bool WorkingSetCommon::fetch(OperationContext* opCtx,
"on the collection.",
"namespace"_attr = ns,
"recordId"_attr = member->recordId,
- "indexKeyData"_attr = indexKeyData);
+ "indexKeyData"_attr = logv2::seqLog(
+ boost::make_transform_iterator(member->keyData.begin(), indexKeyEntryToObjFn),
+ boost::make_transform_iterator(member->keyData.end(), indexKeyEntryToObjFn)));
}
return false;
}
diff --git a/src/mongo/db/exec/write_stage_common.cpp b/src/mongo/db/exec/write_stage_common.cpp
index b0a8db18564..9b8aeb371e3 100644
--- a/src/mongo/db/exec/write_stage_common.cpp
+++ b/src/mongo/db/exec/write_stage_common.cpp
@@ -31,6 +31,7 @@
#include "mongo/db/exec/write_stage_common.h"
#include "mongo/db/catalog/collection.h"
+#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/exec/shard_filterer_impl.h"
#include "mongo/db/exec/working_set.h"
#include "mongo/db/exec/working_set_common.h"
@@ -55,15 +56,14 @@ PreWriteFilter::PreWriteFilter(OperationContext* opCtx, NamespaceString nss)
feature_flags::gFeatureFlagNoChangeStreamEventsDueToOrphans.isEnabled(fcv);
}()),
_skipFiltering([&] {
- // Allow writes on standalone and replica set.
+ // Always allow writes on replica sets.
if (serverGlobalParams.clusterRole == ClusterRole::None) {
return true;
}
- // Only the primary node of a shard that is a replica set should run this filter.
- const auto replCoord = repl::ReplicationCoordinator::get(opCtx);
- return !replCoord->getSettings().usingReplSets() ||
- !replCoord->canAcceptWritesForDatabase(opCtx, NamespaceString::kAdminDb);
+ // Always allow writes on standalone and secondary nodes.
+ const auto replCoord{repl::ReplicationCoordinator::get(opCtx)};
+ return !replCoord->canAcceptWritesForDatabase(opCtx, NamespaceString::kAdminDb);
}()) {}
PreWriteFilter::Action PreWriteFilter::computeAction(const Document& doc) {
diff --git a/src/mongo/db/exec/write_stage_common.h b/src/mongo/db/exec/write_stage_common.h
index 489afa62f2f..5628822efff 100644
--- a/src/mongo/db/exec/write_stage_common.h
+++ b/src/mongo/db/exec/write_stage_common.h
@@ -29,6 +29,8 @@
#pragma once
+#include "mongo/platform/basic.h"
+
#include "mongo/db/exec/shard_filterer.h"
#include "mongo/db/exec/working_set.h"
#include "mongo/db/namespace_string.h"