diff options
author | Svilen Mihaylov <svilen.mihaylov@mongodb.com> | 2022-11-09 15:45:20 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-11-09 16:27:00 +0000 |
commit | 805f480d33180f92e6f6d5fe77bec3d0da10b6df (patch) | |
tree | 24ac8a1cb97eb97efd246a66459b43c105c70b1c | |
parent | 7981ec64d28c49dd2f41d466f7b6bfaccad66fa9 (diff) | |
download | mongo-805f480d33180f92e6f6d5fe77bec3d0da10b6df.tar.gz |
SERVER-70965 [CQF] Strong alias for ProjectionNames and FieldNames
56 files changed, 617 insertions, 398 deletions
diff --git a/src/mongo/db/exec/sbe/abt/abt_lower.cpp b/src/mongo/db/exec/sbe/abt/abt_lower.cpp index deeedabe492..75eb70d21e5 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower.cpp +++ b/src/mongo/db/exec/sbe/abt/abt_lower.cpp @@ -849,18 +849,18 @@ void SBENodeLowering::generateSlots(const FieldProjectionMap& fieldProjectionMap boost::optional<sbe::value::SlotId>& rootSlot, std::vector<std::string>& fields, sbe::value::SlotVector& vars) { - if (!fieldProjectionMap._ridProjection.empty()) { + if (const auto& projName = fieldProjectionMap._ridProjection) { ridSlot = _slotIdGenerator.generate(); - _slotMap.emplace(fieldProjectionMap._ridProjection, ridSlot.value()); + _slotMap.emplace(*projName, ridSlot.value()); } - if (!fieldProjectionMap._rootProjection.empty()) { + if (const auto& projName = fieldProjectionMap._rootProjection) { rootSlot = _slotIdGenerator.generate(); - _slotMap.emplace(fieldProjectionMap._rootProjection, rootSlot.value()); + _slotMap.emplace(*projName, rootSlot.value()); } for (const auto& [fieldName, projectionName] : fieldProjectionMap._fieldProjections) { vars.push_back(_slotIdGenerator.generate()); _slotMap.emplace(projectionName, vars.back()); - fields.push_back(fieldName); + fields.push_back(fieldName.value().toString()); } } diff --git a/src/mongo/db/exec/sbe/abt/abt_lower.h b/src/mongo/db/exec/sbe/abt/abt_lower.h index 819bb8321ca..dfb95048598 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower.h +++ b/src/mongo/db/exec/sbe/abt/abt_lower.h @@ -36,7 +36,7 @@ #include "mongo/db/query/optimizer/utils/utils.h" namespace mongo::optimizer { -using SlotVarMap = stdx::unordered_map<std::string, sbe::value::SlotId>; +using SlotVarMap = stdx::unordered_map<ProjectionName, sbe::value::SlotId, ProjectionName::Hasher>; class SBEExpressionLowering { public: diff --git a/src/mongo/db/pipeline/abt/abt_translate_cq_bm.cpp b/src/mongo/db/pipeline/abt/abt_translate_cq_bm.cpp index 8603ea2d7c0..3a5d0de8df7 100644 --- a/src/mongo/db/pipeline/abt/abt_translate_cq_bm.cpp +++ b/src/mongo/db/pipeline/abt/abt_translate_cq_bm.cpp @@ -59,7 +59,7 @@ public: Metadata metadata{{}}; PrefixId prefixId; - std::string scanProjName = prefixId.getNextId("scan"); + ProjectionName scanProjName{prefixId.getNextId("scan")}; auto findCommand = std::make_unique<FindCommandRequest>(nss); findCommand->setFilter(matchSpec); diff --git a/src/mongo/db/pipeline/abt/abt_translate_pipeline_bm.cpp b/src/mongo/db/pipeline/abt/abt_translate_pipeline_bm.cpp index f878c494c13..842122740bb 100644 --- a/src/mongo/db/pipeline/abt/abt_translate_pipeline_bm.cpp +++ b/src/mongo/db/pipeline/abt/abt_translate_pipeline_bm.cpp @@ -65,7 +65,7 @@ public: Metadata metadata{{}}; PrefixId prefixId; - std::string scanProjName = prefixId.getNextId("scan"); + ProjectionName scanProjName{prefixId.getNextId("scan")}; std::unique_ptr<Pipeline, PipelineDeleter> parsedPipeline = Pipeline::parse(pipeline, expCtx); diff --git a/src/mongo/db/pipeline/abt/agg_expression_visitor.cpp b/src/mongo/db/pipeline/abt/agg_expression_visitor.cpp index 7074915dc16..737671c4c7f 100644 --- a/src/mongo/db/pipeline/abt/agg_expression_visitor.cpp +++ b/src/mongo/db/pipeline/abt/agg_expression_visitor.cpp @@ -256,11 +256,11 @@ public: ABT path = translateFieldPath( fieldPath, make<PathIdentity>(), - [](const std::string& fieldName, const bool isLastElement, ABT input) { + [](FieldNameType fieldName, const bool isLastElement, ABT input) { if (!isLastElement) { input = make<PathTraverse>(std::move(input), PathTraverse::kUnlimited); } - return make<PathGet>(fieldName, std::move(input)); + return make<PathGet>(std::move(fieldName), std::move(input)); }, 1ul); @@ -272,7 +272,7 @@ public: uassert(6624427, "Filter variable must be user-defined.", Variables::isUserDefinedVariable(varId)); - const std::string& varName = generateVariableName(varId); + const ProjectionName varName{generateVariableName(varId)}; _ctx.ensureArity(2); ABT filter = _ctx.pop(); @@ -856,10 +856,13 @@ private: _ctx.push(std::move(current)); } - std::string generateVariableName(const Variables::Id varId) { - std::ostringstream os; - os << _ctx.getUniqueIdPrefix() << "_var_" << varId; - return os.str(); + ProjectionName generateVariableName(const Variables::Id varId) { + str::stream os; + if (const auto& projName = _ctx.getUniqueIdPrefix()) { + os << *projName << "_"; + } + os << "var_" << varId; + return ProjectionName{os}; } void unsupportedExpression(const char* op) const { @@ -884,10 +887,12 @@ private: }; ABT generateAggExpression(const Expression* expr, - const std::string& rootProjection, - const std::string& uniqueIdPrefix) { - ExpressionAlgebrizerContext ctx( - true /*assertExprSort*/, false /*assertPathSort*/, rootProjection, uniqueIdPrefix); + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix) { + ExpressionAlgebrizerContext ctx(true /*assertExprSort*/, + false /*assertPathSort*/, + rootProjection, + std::move(uniqueIdPrefix)); ABTAggExpressionVisitor visitor(ctx); AggExpressionWalker walker(&visitor); diff --git a/src/mongo/db/pipeline/abt/agg_expression_visitor.h b/src/mongo/db/pipeline/abt/agg_expression_visitor.h index b0300dceb63..2971e5d22b1 100644 --- a/src/mongo/db/pipeline/abt/agg_expression_visitor.h +++ b/src/mongo/db/pipeline/abt/agg_expression_visitor.h @@ -36,7 +36,7 @@ namespace mongo::optimizer { ABT generateAggExpression(const Expression* expr, - const std::string& rootProjection, - const std::string& uniqueIdPrefix); + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix); } // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/algebrizer_context.h b/src/mongo/db/pipeline/abt/algebrizer_context.h index c85c7dde0c7..da4034104f2 100644 --- a/src/mongo/db/pipeline/abt/algebrizer_context.h +++ b/src/mongo/db/pipeline/abt/algebrizer_context.h @@ -70,8 +70,8 @@ public: return _node; } - std::string getNextId(const std::string& key) { - return _prefixId.getNextId(key); + ProjectionName getNextId(const std::string& prefix) { + return _prefixId.getNextId(prefix); } PrefixId& getPrefixId() { diff --git a/src/mongo/db/pipeline/abt/collation_translation.cpp b/src/mongo/db/pipeline/abt/collation_translation.cpp index 56bc3345fe7..aa804830324 100644 --- a/src/mongo/db/pipeline/abt/collation_translation.cpp +++ b/src/mongo/db/pipeline/abt/collation_translation.cpp @@ -45,7 +45,8 @@ void generateCollationNode(AlgebrizerContext& ctx, const SortPattern& sortPatter const FieldPath& fieldPath = part.fieldPath.value(); ABT sortPath = make<PathIdentity>(); for (size_t j = 0; j < fieldPath.getPathLength(); j++) { - sortPath = make<PathGet>(fieldPath.getFieldName(j).toString(), std::move(sortPath)); + sortPath = make<PathGet>(FieldNameType{fieldPath.getFieldName(j).toString()}, + std::move(sortPath)); } ctx.setNode<EvaluationNode>( diff --git a/src/mongo/db/pipeline/abt/document_source_visitor.cpp b/src/mongo/db/pipeline/abt/document_source_visitor.cpp index fef377ca443..32727852f3f 100644 --- a/src/mongo/db/pipeline/abt/document_source_visitor.cpp +++ b/src/mongo/db/pipeline/abt/document_source_visitor.cpp @@ -132,7 +132,7 @@ public: std::vector<FieldNameType> groupByFieldNames; for (const auto& [fieldName, expr] : idFields) { - groupByFieldNames.push_back(fieldName); + groupByFieldNames.push_back(FieldNameType{fieldName}); } const bool isSingleIdField = groupByFieldNames.size() == 1 && groupByFieldNames.front() == "_id"; @@ -147,7 +147,7 @@ public: groupByProjNames.push_back(groupByProjName); ABT groupByExpr = generateAggExpression( - idFields.at(fieldName).get(), entry._rootProjection, groupByProjName); + idFields.at(fieldName.value()).get(), entry._rootProjection, groupByProjName); _ctx.setNode<EvaluationNode>(entry._rootProjection, groupByProjName, @@ -157,7 +157,7 @@ public: } // Fields corresponding to each accumulator - ProjectionNameVector aggProjFieldNames; + std::vector<FieldNameType> aggProjFieldNames; // Projection names corresponding to each high-level accumulator ($avg can be broken down // into sum and count.). ProjectionNameVector aggOutputProjNames; @@ -177,10 +177,10 @@ public: std::vector<AvgProjNames> avgProjNames; for (const AccumulationStatement& stmt : accumulatedFields) { - const FieldNameType& fieldName = stmt.fieldName; + const FieldNameType fieldName{stmt.fieldName}; aggProjFieldNames.push_back(fieldName); - ProjectionName aggOutputProjName = _ctx.getNextId(fieldName + "_agg"); + ProjectionName aggOutputProjName{_ctx.getNextId(str::stream() << fieldName << "_agg")}; ABT aggInputExpr = generateAggExpression( stmt.expr.argument.get(), entry._rootProjection, aggOutputProjName); @@ -199,9 +199,11 @@ public: aggOutputProjNames.push_back(aggOutputProjName); if (stmt.makeAccumulator()->getOpName() == "$avg"_sd) { // Express $avg as sum / count. - ProjectionName sumProjName = _ctx.getNextId(fieldName + "_sum_agg"); + ProjectionName sumProjName{ + _ctx.getNextId(str::stream() << fieldName << "_sum_agg")}; aggLowLevelOutputProjNames.push_back(sumProjName); - ProjectionName countProjName = _ctx.getNextId(fieldName + "_count_agg"); + ProjectionName countProjName{ + _ctx.getNextId(str::stream() << fieldName << "_count_agg")}; aggLowLevelOutputProjNames.push_back(countProjName); avgProjNames.emplace_back(AvgProjNames{std::move(aggOutputProjName), std::move(sumProjName), @@ -237,14 +239,14 @@ public: ABT integrationPath = make<PathIdentity>(); for (size_t i = 0; i < groupByFieldNames.size(); i++) { - std::string fieldName = std::move(groupByFieldNames.at(i)); + std::string fieldName = groupByFieldNames.at(i).value().toString(); if (!isSingleIdField) { // Erase '_id.' prefix. fieldName = fieldName.substr(strlen("_id.")); } maybeComposePath(integrationPath, - make<PathField>(std::move(fieldName), + make<PathField>(FieldNameType{std::move(fieldName)}, make<PathConstant>(make<Variable>( std::move(groupByProjNames.at(i)))))); } @@ -260,7 +262,7 @@ public: } entry = _ctx.getNode(); - const std::string& mergeProject = _ctx.getNextId("agg_project"); + const ProjectionName mergeProject{_ctx.getNextId("agg_project")}; _ctx.setNode<EvaluationNode>( mergeProject, mergeProject, @@ -331,9 +333,9 @@ public: ABT localPathGet = translateFieldPath( *localPath, make<PathIdentity>(), - [](const std::string& fieldName, const bool isLastElement, ABT input) { + [](FieldNameType fieldName, const bool isLastElement, ABT input) { return make<PathGet>( - fieldName, + std::move(fieldName), isLastElement ? std::move(input) : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited)); }); @@ -360,9 +362,10 @@ public: ABT foreignSimplePath = translateFieldPath( *foreignPath, make<PathCompare>(Operations::EqMember, make<Variable>(localProjName)), - [](const std::string& fieldName, const bool /*isLastElement*/, ABT input) { + [](FieldNameType fieldName, const bool /*isLastElement*/, ABT input) { return make<PathGet>( - fieldName, make<PathTraverse>(std::move(input), PathTraverse::kSingleLevel)); + std::move(fieldName), + make<PathTraverse>(std::move(input), PathTraverse::kSingleLevel)); }); // Retain only the top-level get into foreignSimplePath. @@ -398,11 +401,11 @@ public: ABT resultPath = translateFieldPath( source->getAsField(), make<PathConstant>(make<Variable>(foreignFoldedProjName)), - [](const std::string& fieldName, const bool isLastElement, ABT input) { + [](FieldNameType fieldName, const bool isLastElement, ABT input) { if (!isLastElement) { input = make<PathTraverse>(std::move(input), PathTraverse::kUnlimited); } - return make<PathField>(fieldName, std::move(input)); + return make<PathField>(std::move(fieldName), std::move(input)); }); const ProjectionName& resultProjName = _ctx.getNextId("result"); @@ -579,8 +582,8 @@ public: const FieldPath& unwindFieldPath = source->getUnwindPath(); const bool preserveNullAndEmpty = source->preserveNullAndEmptyArrays(); - const std::string pidProjName = _ctx.getNextId("unwoundPid"); - const std::string unwoundProjName = _ctx.getNextId("unwoundProj"); + const ProjectionName pidProjName{_ctx.getNextId("unwoundPid")}; + const ProjectionName unwoundProjName{_ctx.getNextId("unwoundProj")}; const auto generatePidGteZeroTest = [&pidProjName](ABT thenCond, ABT elseCond) { return make<If>( @@ -591,7 +594,7 @@ public: ABT embedPath = make<Variable>(unwoundProjName); if (preserveNullAndEmpty) { - const std::string unwindLambdaVarName = _ctx.getNextId("unwoundLambdaVarName"); + const ProjectionName unwindLambdaVarName{_ctx.getNextId("unwoundLambdaVarName")}; embedPath = make<PathLambda>(make<LambdaAbstraction>( unwindLambdaVarName, generatePidGteZeroTest(std::move(embedPath), make<Variable>(unwindLambdaVarName)))); @@ -601,19 +604,19 @@ public: embedPath = translateFieldPath( unwindFieldPath, std::move(embedPath), - [](const std::string& fieldName, const bool isLastElement, ABT input) { + [](FieldNameType fieldName, const bool isLastElement, ABT input) { return make<PathField>( - fieldName, + std::move(fieldName), isLastElement ? std::move(input) : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited)); }); - ABT unwoundPath = translateFieldPath( - unwindFieldPath, - make<PathIdentity>(), - [](const std::string& fieldName, const bool isLastElement, ABT input) { - return make<PathGet>(fieldName, std::move(input)); - }); + ABT unwoundPath = + translateFieldPath(unwindFieldPath, + make<PathIdentity>(), + [](FieldNameType fieldName, const bool isLastElement, ABT input) { + return make<PathGet>(std::move(fieldName), std::move(input)); + }); auto entry = _ctx.getNode(); _ctx.setNode<EvaluationNode>( @@ -630,7 +633,7 @@ public: std::move(entry._node)); entry = _ctx.getNode(); - const std::string embedProjName = _ctx.getNextId("embedProj"); + const ProjectionName embedProjName{_ctx.getNextId("embedProj")}; _ctx.setNode<EvaluationNode>( embedProjName, embedProjName, @@ -644,12 +647,12 @@ public: indexFieldPath, make<PathConstant>( generatePidGteZeroTest(make<Variable>(pidProjName), Constant::null())), - [](const std::string& fieldName, const bool isLastElement, ABT input) { - return make<PathField>(fieldName, std::move(input)); + [](FieldNameType fieldName, const bool /*isLastElement*/, ABT input) { + return make<PathField>(std::move(fieldName), std::move(input)); }); entry = _ctx.getNode(); - const std::string embedPidProjName = _ctx.getNextId("embedPidProj"); + const ProjectionName embedPidProjName{_ctx.getNextId("embedPidProj")}; _ctx.setNode<EvaluationNode>( embedPidProjName, embedPidProjName, diff --git a/src/mongo/db/pipeline/abt/expr_algebrizer_context.cpp b/src/mongo/db/pipeline/abt/expr_algebrizer_context.cpp index ef7e7fab292..045007529a3 100644 --- a/src/mongo/db/pipeline/abt/expr_algebrizer_context.cpp +++ b/src/mongo/db/pipeline/abt/expr_algebrizer_context.cpp @@ -31,15 +31,16 @@ namespace mongo::optimizer { -ExpressionAlgebrizerContext::ExpressionAlgebrizerContext(const bool assertExprSort, - const bool assertPathSort, - const std::string& rootProjection, - const std::string& uniqueIdPrefix) +ExpressionAlgebrizerContext::ExpressionAlgebrizerContext( + const bool assertExprSort, + const bool assertPathSort, + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix) : _assertExprSort(assertExprSort), _assertPathSort(assertPathSort), _rootProjection(rootProjection), _rootProjVar(make<Variable>(_rootProjection)), - _uniqueIdPrefix(uniqueIdPrefix), + _uniqueIdPrefix(std::move(uniqueIdPrefix)), _prefixId() {} void ExpressionAlgebrizerContext::push(ABT node) { @@ -64,7 +65,7 @@ void ExpressionAlgebrizerContext::ensureArity(const size_t arity) { uassert(6624429, "Arity violation", _stack.size() >= arity); } -const std::string& ExpressionAlgebrizerContext::getRootProjection() const { +const ProjectionName& ExpressionAlgebrizerContext::getRootProjection() const { return _rootProjection; } @@ -72,12 +73,15 @@ const ABT& ExpressionAlgebrizerContext::getRootProjVar() const { return _rootProjVar; } -const std::string& ExpressionAlgebrizerContext::getUniqueIdPrefix() const { +const boost::optional<ProjectionName>& ExpressionAlgebrizerContext::getUniqueIdPrefix() const { return _uniqueIdPrefix; } -std::string ExpressionAlgebrizerContext::getNextId(const std::string& key) { - return getUniqueIdPrefix() + "_" + _prefixId.getNextId(key); +ProjectionName ExpressionAlgebrizerContext::getNextId(const std::string& prefix) { + if (const auto& projName = _uniqueIdPrefix) { + return _prefixId.getNextId(str::stream() << *projName << "_" << prefix); + } + return _prefixId.getNextId(prefix); } } // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/expr_algebrizer_context.h b/src/mongo/db/pipeline/abt/expr_algebrizer_context.h index 7c852281078..f36c2954dd6 100644 --- a/src/mongo/db/pipeline/abt/expr_algebrizer_context.h +++ b/src/mongo/db/pipeline/abt/expr_algebrizer_context.h @@ -41,8 +41,8 @@ class ExpressionAlgebrizerContext { public: ExpressionAlgebrizerContext(bool assertExprSort, bool assertPathSort, - const std::string& rootProjection, - const std::string& uniqueIdPrefix); + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix); /** * Push an ABT onto the stack. Optionally perform a check on the type of the ABT based on @@ -64,15 +64,15 @@ public: */ void ensureArity(size_t arity); - const std::string& getRootProjection() const; + const ProjectionName& getRootProjection() const; const ABT& getRootProjVar() const; - const std::string& getUniqueIdPrefix() const; + const boost::optional<ProjectionName>& getUniqueIdPrefix() const; /** - * Returns a unique string for a new projection name. It will be prefixed by 'uniqueIdPrefix'. + * Returns a unique projection. It will be prefixed by 'uniqueIdPrefix'. */ - std::string getNextId(const std::string& key); + ProjectionName getNextId(const std::string& prefix); void enterElemMatch(const MatchExpression::MatchType matchType) { _elemMatchStack.push_back(matchType); @@ -112,11 +112,11 @@ private: const bool _assertPathSort; // The name of the input projection on which the top-level expression will be evaluated. - const std::string _rootProjection; + const ProjectionName _rootProjection; const ABT _rootProjVar; // Used to vend out unique strings for projection names. - const std::string _uniqueIdPrefix; + const boost::optional<ProjectionName> _uniqueIdPrefix; PrefixId _prefixId; // Used to track the parts of the expression tree that have so far been translated to ABT. diff --git a/src/mongo/db/pipeline/abt/field_map_builder.cpp b/src/mongo/db/pipeline/abt/field_map_builder.cpp index 3bb8485b043..e0e86480459 100644 --- a/src/mongo/db/pipeline/abt/field_map_builder.cpp +++ b/src/mongo/db/pipeline/abt/field_map_builder.cpp @@ -34,7 +34,7 @@ namespace mongo::optimizer { void FieldMapBuilder::integrateFieldPath( const FieldPath& fieldPath, const std::function<void(const bool, FieldMapEntry&)>& fn) { std::string path = kRootElement; - auto it = _fieldMap.emplace(path, kRootElement); + auto it = _fieldMap.emplace(path, FieldNameType{kRootElement}); const size_t fieldPathLength = fieldPath.getPathLength(); for (size_t i = 0; i < fieldPathLength; i++) { @@ -42,7 +42,7 @@ void FieldMapBuilder::integrateFieldPath( path += '.' + fieldName; it.first->second._childPaths.insert(path); - it = _fieldMap.emplace(path, fieldName); + it = _fieldMap.emplace(path, FieldNameType{fieldName}); fn(i == fieldPathLength - 1, it.first->second); } } @@ -56,23 +56,23 @@ boost::optional<ABT> FieldMapBuilder::generateABT() const { } ABT FieldMapBuilder::generateABTForField(const FieldMapEntry& entry) const { - const bool isRootEntry = entry._fieldName == kRootElement; + const bool isRootEntry = entry._fieldName.value() == kRootElement; bool hasLeadingObj = false; bool hasTrailingDefault = false; - std::set<std::string> keepSet; - std::set<std::string> dropSet; - std::map<std::string, std::string> varMap; + FieldNameOrderedSet keepSet; + FieldNameOrderedSet dropSet; + std::map<FieldNameType, ProjectionName> varMap; for (const std::string& childField : entry._childPaths) { const FieldMapEntry& childEntry = _fieldMap.at(childField); - const std::string& childFieldName = childEntry._fieldName; + const FieldNameType childFieldName = childEntry._fieldName; if (childEntry._hasKeep) { - keepSet.insert(childFieldName); + keepSet.insert(FieldNameType{childFieldName}); } if (childEntry._hasDrop) { - dropSet.insert(childFieldName); + dropSet.insert(FieldNameType{childFieldName}); } if (childEntry._hasLeadingObj) { hasLeadingObj = true; @@ -80,8 +80,8 @@ ABT FieldMapBuilder::generateABTForField(const FieldMapEntry& entry) const { if (childEntry._hasTrailingDefault) { hasTrailingDefault = true; } - if (!childEntry._constVarName.empty()) { - varMap.emplace(childFieldName, childEntry._constVarName); + if (const auto& constVarName = childEntry._constVarName) { + varMap.emplace(childFieldName, *constVarName); } } @@ -114,7 +114,7 @@ ABT FieldMapBuilder::generateABTForField(const FieldMapEntry& entry) const { ABT childResult = generateABTForField(childEntry); if (!childResult.is<PathIdentity>()) { maybeComposePath(result, - make<PathField>(childEntry._fieldName, + make<PathField>(FieldNameType{childEntry._fieldName}, make<PathTraverse>(std::move(childResult), PathTraverse::kUnlimited))); } diff --git a/src/mongo/db/pipeline/abt/field_map_builder.h b/src/mongo/db/pipeline/abt/field_map_builder.h index c8af6181286..67bf68f907a 100644 --- a/src/mongo/db/pipeline/abt/field_map_builder.h +++ b/src/mongo/db/pipeline/abt/field_map_builder.h @@ -46,16 +46,16 @@ namespace mongo::optimizer { * Obj * Keep "b", "c" */ struct FieldMapEntry { - FieldMapEntry(std::string fieldName) : _fieldName(std::move(fieldName)) { - uassert(6624200, "Empty field name", !_fieldName.empty()); + FieldMapEntry(FieldNameType fieldName) : _fieldName(std::move(fieldName)) { + uassert(6624200, "Empty field name", !_fieldName.value().empty()); } - std::string _fieldName; + FieldNameType _fieldName; bool _hasKeep = false; bool _hasLeadingObj = false; bool _hasTrailingDefault = false; bool _hasDrop = false; - std::string _constVarName; + boost::optional<ProjectionName> _constVarName; // TODO SERVER-68516: Consider maintaining children as a vector of FieldMapEntry's. Then we can // remove the _fieldMap member of FieldMapBuilder. diff --git a/src/mongo/db/pipeline/abt/match_expression_visitor.cpp b/src/mongo/db/pipeline/abt/match_expression_visitor.cpp index 70be6fd3651..0acce0d1a2a 100644 --- a/src/mongo/db/pipeline/abt/match_expression_visitor.cpp +++ b/src/mongo/db/pipeline/abt/match_expression_visitor.cpp @@ -418,7 +418,7 @@ public: void visit(const SizeMatchExpression* expr) override { assertSupportedPathExpression(expr); - const std::string lambdaProjName = _ctx.getNextId("lambda_sizeMatch"); + const ProjectionName lambdaProjName{_ctx.getNextId("lambda_sizeMatch")}; ABT result = make<PathLambda>(make<LambdaAbstraction>( lambdaProjName, make<BinaryOp>( @@ -447,7 +447,7 @@ public: void visit(const TypeMatchExpression* expr) override { assertSupportedPathExpression(expr); - const std::string lambdaProjName = _ctx.getNextId("lambda_typeMatch"); + const ProjectionName lambdaProjName{_ctx.getNextId("lambda_typeMatch")}; ABT result = make<PathLambda>(make<LambdaAbstraction>( lambdaProjName, make<FunctionCall>("typeMatch", @@ -656,10 +656,12 @@ private: ABT generateMatchExpression(const MatchExpression* expr, const bool allowAggExpressions, - const std::string& rootProjection, - const std::string& uniqueIdPrefix) { - ExpressionAlgebrizerContext ctx( - false /*assertExprSort*/, true /*assertPathSort*/, rootProjection, uniqueIdPrefix); + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix) { + ExpressionAlgebrizerContext ctx(false /*assertExprSort*/, + true /*assertPathSort*/, + rootProjection, + std::move(uniqueIdPrefix)); ABTMatchExpressionPreVisitor preVisitor(ctx); ABTMatchExpressionVisitor postVisitor(ctx, allowAggExpressions); MatchExpressionWalker walker(&preVisitor, nullptr /*inVisitor*/, &postVisitor); diff --git a/src/mongo/db/pipeline/abt/match_expression_visitor.h b/src/mongo/db/pipeline/abt/match_expression_visitor.h index d971ffd5ec6..c23e0f772c8 100644 --- a/src/mongo/db/pipeline/abt/match_expression_visitor.h +++ b/src/mongo/db/pipeline/abt/match_expression_visitor.h @@ -39,7 +39,7 @@ namespace mongo::optimizer { */ ABT generateMatchExpression(const MatchExpression* expr, bool allowAggExpressions, - const std::string& rootProjection, - const std::string& uniqueIdPrefix); + const ProjectionName& rootProjection, + boost::optional<ProjectionName> uniqueIdPrefix); } // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.cpp b/src/mongo/db/pipeline/abt/transformer_visitor.cpp index ccfbeb56e31..101e2f24d6e 100644 --- a/src/mongo/db/pipeline/abt/transformer_visitor.cpp +++ b/src/mongo/db/pipeline/abt/transformer_visitor.cpp @@ -58,7 +58,7 @@ void ABTTransformerVisitor::visit(const GroupFromFirstDocumentTransformation* tr void ABTTransformerVisitor::visit(const ReplaceRootTransformation* transformer) { auto entry = _ctx.getNode(); - const std::string& projName = _ctx.getNextId("newRoot"); + const ProjectionName projName{_ctx.getNextId("newRoot")}; ABT expr = generateAggExpression(transformer->getExpression().get(), entry._rootProjection, projName); @@ -112,7 +112,7 @@ void ABTTransformerVisitor::processProjectedPaths(const projection_executor::Inc * Handles renamed fields and computed projections. */ void ABTTransformerVisitor::processComputedPaths(const projection_executor::InclusionNode& node, - const std::string& rootProjection, + const ProjectionName& rootProjection, const bool isAddingFields) { OrderedPathSet computedPaths; StringMap<std::string> renamedPaths; @@ -123,15 +123,15 @@ void ABTTransformerVisitor::processComputedPaths(const projection_executor::Incl ABT path = translateFieldPath( FieldPath(renamedPathEntry.second), make<PathIdentity>(), - [](const std::string& fieldName, const bool isLastElement, ABT input) { + [](FieldNameType fieldName, const bool isLastElement, ABT input) { return make<PathGet>( - fieldName, + std::move(fieldName), isLastElement ? std::move(input) : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited)); }); auto entry = _ctx.getNode(); - const std::string& renamedProjName = _ctx.getNextId("projRenamedPath"); + const ProjectionName renamedProjName{_ctx.getNextId("projRenamedPath")}; _ctx.setNode<EvaluationNode>( entry._rootProjection, renamedProjName, @@ -158,7 +158,7 @@ void ABTTransformerVisitor::processComputedPaths(const projection_executor::Incl const FieldPath computedPath(computedPathStr); auto entry = _ctx.getNode(); - const std::string& getProjName = _ctx.getNextId("projGetPath"); + const ProjectionName getProjName{_ctx.getNextId("projGetPath")}; ABT getExpr = generateAggExpression( node.getExpressionForPath(computedPath).get(), rootProjection, getProjName); @@ -184,7 +184,7 @@ void ABTTransformerVisitor::processComputedPaths(const projection_executor::Incl void ABTTransformerVisitor::visitInclusionNode(const projection_executor::InclusionNode& node, const bool isAddingFields) { auto entry = _ctx.getNode(); - const std::string rootProjection = entry._rootProjection; + const ProjectionName rootProjection{entry._rootProjection}; processProjectedPaths(node); processComputedPaths(node, rootProjection, isAddingFields); diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.h b/src/mongo/db/pipeline/abt/transformer_visitor.h index 8313454d540..0ebc00e3308 100644 --- a/src/mongo/db/pipeline/abt/transformer_visitor.h +++ b/src/mongo/db/pipeline/abt/transformer_visitor.h @@ -76,7 +76,7 @@ private: * Handles renamed fields and computed projections. */ void processComputedPaths(const projection_executor::InclusionNode& node, - const std::string& rootProjection, + const ProjectionName& rootProjection, bool isAddingFields); void visitInclusionNode(const projection_executor::InclusionNode& node, bool isAddingFields); diff --git a/src/mongo/db/pipeline/abt/utils.cpp b/src/mongo/db/pipeline/abt/utils.cpp index 9911e090305..e8be71b900a 100644 --- a/src/mongo/db/pipeline/abt/utils.cpp +++ b/src/mongo/db/pipeline/abt/utils.cpp @@ -73,8 +73,8 @@ ABT translateFieldPath(const FieldPath& fieldPath, const size_t fieldPathLength = fieldPath.getPathLength(); bool isLastElement = true; for (size_t i = fieldPathLength; i-- > skipFromStart;) { - result = - fieldNameFn(fieldPath.getFieldName(i).toString(), isLastElement, std::move(result)); + result = fieldNameFn( + FieldNameType{fieldPath.getFieldName(i).toString()}, isLastElement, std::move(result)); isLastElement = false; } @@ -108,7 +108,7 @@ ABT translateFieldRef(const FieldRef& fieldRef, ABT initial) { result = make<PathTraverse>(std::move(result), PathTraverse::kSingleLevel); } } - result = make<PathGet>(fieldRef[i].toString(), std::move(result)); + result = make<PathGet>(FieldNameType{fieldRef[i].toString()}, std::move(result)); } return result; diff --git a/src/mongo/db/pipeline/abt/utils.h b/src/mongo/db/pipeline/abt/utils.h index 54a255adff7..23ce980662f 100644 --- a/src/mongo/db/pipeline/abt/utils.h +++ b/src/mongo/db/pipeline/abt/utils.h @@ -39,7 +39,7 @@ namespace mongo::optimizer { std::pair<sbe::value::TypeTags, sbe::value::Value> convertFrom(Value val); using ABTFieldNameFn = - std::function<ABT(const std::string& fieldName, const bool isLastElement, ABT input)>; + std::function<ABT(FieldNameType fieldName, const bool isLastElement, ABT input)>; /** * Translates an aggregation FieldPath by invoking the `fieldNameFn` for each path component. diff --git a/src/mongo/db/query/ce/ce_dataflow_nodes_test.cpp b/src/mongo/db/query/ce/ce_dataflow_nodes_test.cpp index 9a59257e4bb..1f11472c811 100644 --- a/src/mongo/db/query/ce/ce_dataflow_nodes_test.cpp +++ b/src/mongo/db/query/ce/ce_dataflow_nodes_test.cpp @@ -81,7 +81,7 @@ TEST(CEDataflowTest, EstimateTrivialNodes) { TEST(CEDataflowTest, EstimateUnionNode) { auto makeUnionBranch = [](const std::string& collName) { - auto scanVar = "scan_" + collName; + ProjectionName scanVar{"scan_" + collName}; auto scanNode = make<ScanNode>(scanVar, collName); auto evalPath = make<EvalPath>(make<PathGet>("a", make<PathIdentity>()), make<Variable>(scanVar)); diff --git a/src/mongo/db/query/ce/ce_heuristic.cpp b/src/mongo/db/query/ce/ce_heuristic.cpp index 2c2ab13fb86..5e5712064ba 100644 --- a/src/mongo/db/query/ce/ce_heuristic.cpp +++ b/src/mongo/db/query/ce/ce_heuristic.cpp @@ -260,7 +260,7 @@ public: // Each item represents a field in a dotted path. // Collected while traversing a path expression. // Used for deciding whether a conjunction of comparisons is an interval or not. - std::vector<std::string> path; + FieldPathType path; // When handling a PathComposeM, we need to access its child comparisons which might be // hidden under path expressions. const PathCompare* compare; diff --git a/src/mongo/db/query/ce/ce_histogram_test.cpp b/src/mongo/db/query/ce/ce_histogram_test.cpp index a51b054bdf6..7c9e8fcc402 100644 --- a/src/mongo/db/query/ce/ce_histogram_test.cpp +++ b/src/mongo/db/query/ce/ce_histogram_test.cpp @@ -730,12 +730,13 @@ TEST(CEHistogramTest, TestArrayHistogramOnCompositePredicates) { ASSERT_MATCH_CE_NODE(t, "{mixed: {$elemMatch: {}}}", 88.0, isSargable); // Take into account both empty and non-empty arrays. - auto makePathArrABT = [&](const std::string& path) { - const auto scanProjection = "scan_0"; + auto makePathArrABT = [&](const FieldNameType& fieldName) { + const ProjectionName scanProjection{"scan_0"}; auto scanNode = make<ScanNode>(scanProjection, collName); - auto filterNode = make<FilterNode>( - make<EvalFilter>(make<PathGet>(path, make<PathArr>()), make<Variable>(scanProjection)), - std::move(scanNode)); + auto filterNode = + make<FilterNode>(make<EvalFilter>(make<PathGet>(std::move(fieldName), make<PathArr>()), + make<Variable>(scanProjection)), + std::move(scanNode)); return make<RootNode>( properties::ProjectionRequirement{ProjectionNameVector{scanProjection}}, std::move(filterNode)); diff --git a/src/mongo/db/query/cqf_get_executor.cpp b/src/mongo/db/query/cqf_get_executor.cpp index 2aed4f9304c..c16bb715af5 100644 --- a/src/mongo/db/query/cqf_get_executor.cpp +++ b/src/mongo/db/query/cqf_get_executor.cpp @@ -220,7 +220,7 @@ static opt::unordered_map<std::string, optimizer::IndexDefinition> buildIndexSpe ABT exprABT = generateMatchExpression(expr.get(), false /*allowAggExpression*/, "<root>" /*rootProjection*/, - "" /*uniquePrefix*/); + boost::none /*uniquePrefix*/); exprABT = make<EvalFilter>(std::move(exprABT), make<Variable>(scanProjName)); // TODO SERVER-70315: simplify partial filter expression. diff --git a/src/mongo/db/query/optimizer/cascades/enforcers.cpp b/src/mongo/db/query/optimizer/cascades/enforcers.cpp index 801ae716202..33a57d32424 100644 --- a/src/mongo/db/query/optimizer/cascades/enforcers.cpp +++ b/src/mongo/db/query/optimizer/cascades/enforcers.cpp @@ -190,7 +190,7 @@ public: const ProjectionNameSet& availableProjections = getPropertyConst<ProjectionAvailability>(_logicalProps).getProjections(); - ProjectionName ridProjName; + boost::optional<ProjectionName> ridProjName; if (hasProperty<IndexingAvailability>(_logicalProps)) { const auto& scanDefName = getPropertyConst<IndexingAvailability>(_logicalProps).getScanDefName(); diff --git a/src/mongo/db/query/optimizer/cascades/implementers.cpp b/src/mongo/db/query/optimizer/cascades/implementers.cpp index 835045a2611..e5e2635c1fb 100644 --- a/src/mongo/db/query/optimizer/cascades/implementers.cpp +++ b/src/mongo/db/query/optimizer/cascades/implementers.cpp @@ -172,12 +172,12 @@ public: const auto& requiredProjections = getPropertyConst<ProjectionRequirement>(_physProps).getProjections(); - ProjectionName ridProjName; + boost::optional<ProjectionName> ridProjName; bool needsRID = false; if (hasProperty<IndexingAvailability>(_logicalProps)) { ridProjName = _ridProjections.at( getPropertyConst<IndexingAvailability>(_logicalProps).getScanDefName()); - needsRID = requiredProjections.find(ridProjName).second; + needsRID = requiredProjections.find(*ridProjName).second; } if (needsRID && !node.getHasRID()) { // We cannot provide RID. @@ -201,7 +201,7 @@ public: } if (needsRID) { physNode = make<EvaluationNode>( - std::move(ridProjName), Constant::nothing(), std::move(physNode)); + std::move(*ridProjName), Constant::nothing(), std::move(physNode)); nodeCEMap.emplace(physNode.cast<Node>(), 0.0); } } else { @@ -210,7 +210,7 @@ public: physNode = make<LimitSkipNode>(LimitSkipRequirement{1, 0}, std::move(physNode)); nodeCEMap.emplace(physNode.cast<Node>(), 1.0); - const ProjectionName valueScanProj = _prefixId.getNextId("valueScan"); + const ProjectionName valueScanProj{_prefixId.getNextId("valueScan")}; physNode = make<EvaluationNode>(valueScanProj, node.getValueArray(), std::move(physNode)); nodeCEMap.emplace(physNode.cast<Node>(), 1.0); @@ -245,7 +245,7 @@ public: if (needsRID) { // Obtain row id from first element of the array. physNode = make<EvaluationNode>( - std::move(ridProjName), getElementFn(0), std::move(physNode)); + std::move(*ridProjName), getElementFn(0), std::move(physNode)); nodeCEMap.emplace(physNode.cast<Node>(), node.getArraySize()); } } @@ -269,7 +269,7 @@ public: return; } - VariableNameSetType references = collectVariableReferences(n); + ProjectionNameSet references = collectVariableReferences(n); if (checkIntroducesScanProjectionUnderIndexOnly(references)) { // Reject if under indexing requirements and now we introduce dependence on scan // projection. @@ -349,7 +349,7 @@ public: // requirement. PhysProps newProps = _physProps; - VariableNameSetType references = collectVariableReferences(n); + ProjectionNameSet references = collectVariableReferences(n); if (checkIntroducesScanProjectionUnderIndexOnly(references)) { // Reject if under indexing requirements and now we introduce dependence on scan // projection. @@ -572,8 +572,9 @@ public: !areCompoundIntervalsEqualities(*singularInterval) && indexDef.isMultiKey() && requirements.getDedupRID(); - indexProjectionMap._ridProjection = - (needsRID || needsUniqueStage) ? ridProjName : ""; + if (needsRID || needsUniqueStage) { + indexProjectionMap._ridProjection = ridProjName; + } if (singularInterval) { physNode = make<IndexScanNode>(std::move(indexProjectionMap), @@ -943,7 +944,7 @@ public: getPropertyConst<ProjectionRequirement>(_physProps).getProjections(); // Add expression references to requirements. - VariableNameSetType references = collectVariableReferences(n); + ProjectionNameSet references = collectVariableReferences(n); for (const auto& varName : references) { reqProjections.emplace_back(varName); } @@ -978,7 +979,7 @@ public: // Split collation between inner and outer side. const CollationSplitResult& collationSplit = splitCollationSpec( - "" /*ridProjName*/, collationSpec, leftProjections, rightProjections); + boost::none /*ridProjName*/, collationSpec, leftProjections, rightProjections); if (!collationSplit._validSplit) { return; } @@ -1102,7 +1103,7 @@ public: // Iterate over the aggregation expressions and only add those required. ABTVector aggregationProjections; ProjectionNameVector aggregationProjectionNames; - VariableNameSetType projectionsToAdd; + ProjectionNameSet projectionsToAdd; for (const ProjectionName& groupByProjName : groupByProjections) { projectionsToAdd.insert(groupByProjName); } @@ -1310,7 +1311,7 @@ private: const bool needsCollation = candidateIndexEntry._fieldsToCollate.count(indexField) > 0; - auto it = fieldProjections.find(encodeIndexKeyName(indexField)); + auto it = fieldProjections.find(FieldNameType{encodeIndexKeyName(indexField)}); if (it == fieldProjections.cend()) { // No bound projection for this index field. if (needsCollation) { @@ -1369,7 +1370,7 @@ private: * Check if we are under index-only requirements and expression introduces dependency on scan * projection. */ - bool checkIntroducesScanProjectionUnderIndexOnly(const VariableNameSetType& references) { + bool checkIntroducesScanProjectionUnderIndexOnly(const ProjectionNameSet& references) { return hasProperty<IndexingAvailability>(_logicalProps) && getPropertyConst<IndexingRequirement>(_physProps).getIndexReqTarget() == IndexReqTarget::Index && diff --git a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp index 0c9a923b2ed..77d9fd71c53 100644 --- a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp +++ b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp @@ -319,7 +319,7 @@ ReorderDependencies computeDependencies(ABT::reference_type aboveNodeRef, env.hasDefinitions(belowChild) ? env.getDefinitions(belowChild) : DefinitionsMap{}; ReorderDependencies dependencies; - for (const std::string& varName : aboveNodeVarNames) { + for (const ProjectionName& varName : aboveNodeVarNames) { auto it = belowNodeDefs.find(varName); // Variable is exclusively defined in the below node. const bool refersToNode = it != belowNodeDefs.cend() && it->second.definedBy == belowNode; @@ -696,7 +696,7 @@ static void convertFilterToSargableNode(ABT::reference_type node, for (auto it = conversion->_reqMap.cbegin(); it != conversion->_reqMap.cend();) { uassert(6624111, "Filter partial schema requirement must contain a variable name.", - !it->first._projectionName.empty()); + it->first._projectionName); uassert(6624112, "Filter partial schema requirement cannot bind.", !it->second.getBoundProjectionName()); @@ -844,12 +844,12 @@ struct SubstituteConvert<EvaluationNode> { ABT result = evalNode.getChild(); ABT keepPath = make<PathIdentity>(); - std::set<std::string> orderedSet; - for (const std::string& fieldName : pathKeepPtr->getNames()) { + FieldNameOrderedSet orderedSet; + for (const FieldNameType& fieldName : pathKeepPtr->getNames()) { orderedSet.insert(fieldName); } - for (const std::string& fieldName : orderedSet) { - ProjectionName projName = ctx.getPrefixId().getNextId("fieldProj"); + for (const FieldNameType& fieldName : orderedSet) { + ProjectionName projName{ctx.getPrefixId().getNextId("fieldProj")}; result = make<EvaluationNode>( projName, make<EvalPath>(make<PathGet>(fieldName, make<PathIdentity>()), @@ -892,7 +892,7 @@ struct SubstituteConvert<EvaluationNode> { uassert(6624114, "Eval partial schema requirement must contain a variable name.", - !key._projectionName.empty()); + key._projectionName); uassert(6624115, "Eval partial schema requirement cannot have a range", isIntervalReqFullyOpenDNF(req.getIntervals())); @@ -960,7 +960,7 @@ static SplitRequirementsResult splitRequirements( const bool disableYieldingTolerantPlans, const std::vector<bool>& isFullyOpen, const std::vector<bool>& mayReturnNull, - const boost::optional<opt::unordered_set<FieldNameType>>& indexFieldPrefixMapForScanDef, + const boost::optional<FieldNameSet>& indexFieldPrefixMapForScanDef, const PartialSchemaRequirements& reqMap) { SplitRequirementsResult result; auto& leftReqs = result._leftReqs; @@ -1071,7 +1071,7 @@ struct ExploreConvert<SargableNode> { } const ProjectionName& scanProjectionName = indexingAvailability.getScanProjection(); - if (collectVariableReferences(node) != VariableNameSetType{scanProjectionName}) { + if (collectVariableReferences(node) != ProjectionNameSet{scanProjectionName}) { // Rewrite not applicable if we refer projections other than the scan projection. return; } @@ -1079,7 +1079,7 @@ struct ExploreConvert<SargableNode> { const bool isIndex = target == IndexReqTarget::Index; const auto& indexFieldPrefixMap = ctx.getIndexFieldPrefixMap(); - boost::optional<opt::unordered_set<FieldNameType>> indexFieldPrefixMapForScanDef; + boost::optional<FieldNameSet> indexFieldPrefixMapForScanDef; if (auto it = indexFieldPrefixMap.find(scanDefName); it != indexFieldPrefixMap.cend() && !isIndex) { indexFieldPrefixMapForScanDef = it->second; diff --git a/src/mongo/db/query/optimizer/cascades/logical_rewriter.h b/src/mongo/db/query/optimizer/cascades/logical_rewriter.h index 9ffa602da23..0af43abae90 100644 --- a/src/mongo/db/query/optimizer/cascades/logical_rewriter.h +++ b/src/mongo/db/query/optimizer/cascades/logical_rewriter.h @@ -136,7 +136,7 @@ private: // Contains the set of top-level index fields for a given scanDef. For example "a.b" is encoded // as "a". This is used to constrain the possible splits of a sargable node. - opt::unordered_map<std::string, opt::unordered_set<FieldNameType>> _indexFieldPrefixMap; + opt::unordered_map<std::string, FieldNameSet> _indexFieldPrefixMap; // Track number of times a SargableNode at a given position in the memo has been split. opt::unordered_map<MemoLogicalNodeId, size_t, NodeIdHash> _sargableSplitCountMap; diff --git a/src/mongo/db/query/optimizer/defs.cpp b/src/mongo/db/query/optimizer/defs.cpp index d3de6784107..80f51efb8c9 100644 --- a/src/mongo/db/query/optimizer/defs.cpp +++ b/src/mongo/db/query/optimizer/defs.cpp @@ -33,9 +33,9 @@ namespace mongo::optimizer { -static opt::unordered_map<ProjectionName, size_t> createMapFromVector( +static opt::unordered_map<ProjectionName, size_t, ProjectionName::Hasher> createMapFromVector( const ProjectionNameVector& v) { - opt::unordered_map<ProjectionName, size_t> result; + opt::unordered_map<ProjectionName, size_t, ProjectionName::Hasher> result; for (size_t i = 0; i < v.size(); i++) { result.emplace(v.at(i), i); } @@ -95,7 +95,7 @@ bool ProjectionNameOrderPreservingSet::erase(const ProjectionName& projectionNam } _map.erase(projectionName); - _vector.resize(_vector.size() - 1); + _vector.pop_back(); return true; } diff --git a/src/mongo/db/query/optimizer/defs.h b/src/mongo/db/query/optimizer/defs.h index 2cf6dbccd10..c3d63967750 100644 --- a/src/mongo/db/query/optimizer/defs.h +++ b/src/mongo/db/query/optimizer/defs.h @@ -29,6 +29,7 @@ #pragma once +#include <boost/optional.hpp> #include <set> #include <sstream> #include <string> @@ -36,20 +37,36 @@ #include "mongo/db/query/optimizer/containers.h" #include "mongo/db/query/optimizer/utils/printable_enum.h" +#include "mongo/db/query/optimizer/utils/strong_string_alias.h" namespace mongo::optimizer { -using FieldNameType = std::string; +/** + * Representation of a field name. Can be empty. + */ +struct FieldNameAliasTag { + static constexpr bool kAllowEmpty = true; +}; +using FieldNameType = StrongStringAlias<FieldNameAliasTag>; + using FieldPathType = std::vector<FieldNameType>; +using FieldNameOrderedSet = std::set<FieldNameType>; +using FieldNameSet = opt::unordered_set<FieldNameType, FieldNameType::Hasher>; -using CollectionNameType = std::string; +/** + * Representation of a variable name. Cannot be empty. + */ +struct ProjectionNameAliasTag { + static constexpr bool kAllowEmpty = false; +}; +using ProjectionName = StrongStringAlias<ProjectionNameAliasTag>; -using ProjectionName = std::string; -using ProjectionNameSet = opt::unordered_set<ProjectionName>; +using ProjectionNameSet = opt::unordered_set<ProjectionName, ProjectionName::Hasher>; using ProjectionNameOrderedSet = std::set<ProjectionName>; using ProjectionNameVector = std::vector<ProjectionName>; -using ProjectionRenames = opt::unordered_map<ProjectionName, ProjectionName>; +using ProjectionRenames = + opt::unordered_map<ProjectionName, ProjectionName, ProjectionName::Hasher>; // Map from scanDefName to rid projection name. using RIDProjectionsMap = opt::unordered_map<std::string, ProjectionName>; @@ -73,7 +90,7 @@ public: const ProjectionNameVector& getVector() const; private: - opt::unordered_map<ProjectionName, size_t> _map; + opt::unordered_map<ProjectionName, size_t, ProjectionName::Hasher> _map; ProjectionNameVector _vector; }; @@ -101,9 +118,9 @@ MAKE_PRINTABLE_ENUM_STRING_ARRAY(DistributionTypeEnum, DistributionType, DISTRIB // In case of covering scan, index, or fetch, specify names of bound projections for each field. // Also optionally specify if applicable the rid and record (root) projections. struct FieldProjectionMap { - ProjectionName _ridProjection; - ProjectionName _rootProjection; - opt::unordered_map<FieldNameType, ProjectionName> _fieldProjections; + boost::optional<ProjectionName> _ridProjection; + boost::optional<ProjectionName> _rootProjection; + opt::unordered_map<FieldNameType, ProjectionName, FieldNameType::Hasher> _fieldProjections; bool operator==(const FieldProjectionMap& other) const; }; diff --git a/src/mongo/db/query/optimizer/explain.cpp b/src/mongo/db/query/optimizer/explain.cpp index 5e9df5df3bb..6432ae3e9b3 100644 --- a/src/mongo/db/query/optimizer/explain.cpp +++ b/src/mongo/db/query/optimizer/explain.cpp @@ -124,6 +124,12 @@ public: return *this; } + template <class TagType> + ExplainPrinterImpl& print(const StrongStringAlias<TagType>& t) { + print(t.value().empty() ? "<empty>" : t.value()); + return *this; + } + /** * Here and below: "other" printer(s) may be siphoned out. */ @@ -414,8 +420,13 @@ public: } ExplainPrinterImpl& print(const std::string& s) { - auto [tag, val] = sbe::value::makeNewString(s); - addValue(tag, val); + printStringInternal(s); + return *this; + } + + template <class TagType> + ExplainPrinterImpl& print(const StrongStringAlias<TagType>& s) { + printStringInternal(s.value().toString()); return *this; } @@ -458,12 +469,26 @@ public: return *this; } + template <size_t N> + ExplainPrinterImpl& fieldName(const char (&name)[N], + const ExplainVersion minVersion = ExplainVersion::V1, + const ExplainVersion maxVersion = ExplainVersion::Vmax) { + fieldNameInternal(name, minVersion, maxVersion); + return *this; + } + ExplainPrinterImpl& fieldName(const std::string& name, const ExplainVersion minVersion = ExplainVersion::V1, const ExplainVersion maxVersion = ExplainVersion::Vmax) { - if (minVersion <= version && maxVersion >= version) { - _nextFieldName = name; - } + fieldNameInternal(name, minVersion, maxVersion); + return *this; + } + + template <class TagType> + ExplainPrinterImpl& fieldName(const StrongStringAlias<TagType>& name, + const ExplainVersion minVersion = ExplainVersion::V1, + const ExplainVersion maxVersion = ExplainVersion::Vmax) { + fieldNameInternal(name.value().toString(), minVersion, maxVersion); return *this; } @@ -473,6 +498,21 @@ public: } private: + ExplainPrinterImpl& printStringInternal(const std::string& s) { + auto [tag, val] = sbe::value::makeNewString(s); + addValue(tag, val); + return *this; + } + + ExplainPrinterImpl& fieldNameInternal(const std::string& name, + const ExplainVersion minVersion, + const ExplainVersion maxVersion) { + if (minVersion <= version && maxVersion >= version) { + _nextFieldName = name; + } + return *this; + } + ExplainPrinterImpl& print(ExplainPrinterImpl& other, const bool append) { auto [tag, val] = other.moveValue(); addValue(tag, val, append); @@ -674,7 +714,7 @@ public: ExplainPrinter transport(const ABT& /*n*/, const ExpressionBinder& binders, std::vector<ExplainPrinter> inResults) { - std::map<std::string, ExplainPrinter> ordered; + std::map<ProjectionName, ExplainPrinter> ordered; for (size_t idx = 0; idx < inResults.size(); ++idx) { ordered.emplace(binders.names()[idx], std::move(inResults[idx])); } @@ -699,11 +739,11 @@ public: static void printFieldProjectionMap(ExplainPrinter& printer, const FieldProjectionMap& map) { std::map<FieldNameType, ProjectionName> ordered; - if (!map._ridProjection.empty()) { - ordered["<rid>"] = map._ridProjection; + if (const auto& projName = map._ridProjection) { + ordered.emplace("<rid>", *projName); } - if (!map._rootProjection.empty()) { - ordered["<root>"] = map._rootProjection; + if (const auto& projName = map._rootProjection) { + ordered.emplace("<root>", *projName); } for (const auto& entry : map._fieldProjections) { ordered.insert(entry); @@ -1085,7 +1125,9 @@ public: for (const auto& [key, req] : reqMap) { ExplainPrinter local; - local.fieldName("refProjection").print(key._projectionName).separator(", "); + if (const auto& projName = key._projectionName) { + local.fieldName("refProjection").print(*projName).separator(", "); + } ExplainPrinter pathPrinter = generate(key._path); local.fieldName("path").separator("'").printSingleLevel(pathPrinter).separator("', "); @@ -1113,7 +1155,9 @@ public: for (const auto& [key, req, entryIndex] : residualReqs) { ExplainPrinter local; - local.fieldName("refProjection").print(key._projectionName).separator(", "); + if (const auto& projName = key._projectionName) { + local.fieldName("refProjection").print(*projName).separator(", "); + } ExplainPrinter pathPrinter = generate(key._path); local.fieldName("path").separator("'").printSingleLevel(pathPrinter).separator("', "); @@ -1733,10 +1777,10 @@ public: ExplainPrinter pathPrinter = gen.generate(key._path); ExplainPrinter local; - local.fieldName("refProjection") - .print(key._projectionName) - .separator(", ") - .fieldName("path") + if (const auto& projName = key._projectionName) { + local.fieldName("refProjection").print(*projName).separator(", "); + } + local.fieldName("path") .separator("'") .printSingleLevel(pathPrinter) .separator("', ") @@ -2172,10 +2216,10 @@ public: return printer; } - static void printPathProjections(ExplainPrinter& printer, const std::set<std::string>& names) { + static void printPathProjections(ExplainPrinter& printer, const FieldNameOrderedSet& names) { if constexpr (version < ExplainVersion::V3) { bool first = true; - for (const std::string& s : names) { + for (const FieldNameType& s : names) { if (first) { first = false; } else { @@ -2185,7 +2229,7 @@ public: } } else if constexpr (version == ExplainVersion::V3) { std::vector<ExplainPrinter> printers; - for (const std::string& s : names) { + for (const FieldNameType& s : names) { ExplainPrinter local; local.print(s); printers.push_back(std::move(local)); @@ -2263,7 +2307,7 @@ public: ExplainPrinter printer("PathGet"); printer.separator(" [") .fieldName("path", ExplainVersion::V3) - .print(path.name().empty() ? "<empty>" : path.name()) + .print(path.name()) .separator("]") .setChildCount(1) .fieldName("input", ExplainVersion::V3) diff --git a/src/mongo/db/query/optimizer/index_bounds.cpp b/src/mongo/db/query/optimizer/index_bounds.cpp index 918158a25a6..3a7161e1510 100644 --- a/src/mongo/db/query/optimizer/index_bounds.cpp +++ b/src/mongo/db/query/optimizer/index_bounds.cpp @@ -105,7 +105,13 @@ void IntervalRequirement::reverse() { std::swap(_lowBound, _highBound); } +PartialSchemaKey::PartialSchemaKey(ABT path) : PartialSchemaKey(boost::none, std::move(path)) {} + PartialSchemaKey::PartialSchemaKey(ProjectionName projectionName, ABT path) + : PartialSchemaKey(boost::optional<ProjectionName>{std::move(projectionName)}, + std::move(path)) {} + +PartialSchemaKey::PartialSchemaKey(boost::optional<ProjectionName> projectionName, ABT path) : _projectionName(std::move(projectionName)), _path(std::move(path)) { assertPathSort(_path); } @@ -131,8 +137,6 @@ PartialSchemaRequirement::PartialSchemaRequirement( tassert(6624154, "Cannot have perf only requirement which also binds", !_isPerfOnly || !_boundProjectionName); - tassert( - 6624155, "Empty projection name", !_boundProjectionName || !_boundProjectionName->empty()); } bool PartialSchemaRequirement::operator==(const PartialSchemaRequirement& other) const { @@ -162,10 +166,20 @@ bool IndexPath3WComparator::operator()(const ABT& path1, const ABT& path2) const bool PartialSchemaKeyLessComparator::operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const { - const int projCmp = k1._projectionName.compare(k2._projectionName); - if (projCmp != 0) { - return projCmp < 0; + if (const auto& p1 = k1._projectionName) { + if (const auto& p2 = k2._projectionName) { + const int projCmp = p1->compare(*p2); + if (projCmp != 0) { + return projCmp < 0; + } + // Fallthrough to comparison below. + } else { + return false; + } + } else if (k2._projectionName) { + return false; } + return compareExprAndPaths(k1._path, k2._path) < 0; } diff --git a/src/mongo/db/query/optimizer/index_bounds.h b/src/mongo/db/query/optimizer/index_bounds.h index 685a26b045f..05599a87395 100644 --- a/src/mongo/db/query/optimizer/index_bounds.h +++ b/src/mongo/db/query/optimizer/index_bounds.h @@ -80,12 +80,14 @@ private: }; struct PartialSchemaKey { + PartialSchemaKey(ABT path); PartialSchemaKey(ProjectionName projectionName, ABT path); + PartialSchemaKey(boost::optional<ProjectionName> projectionName, ABT path); bool operator==(const PartialSchemaKey& other) const; // Referred, or input projection name. - ProjectionName _projectionName; + boost::optional<ProjectionName> _projectionName; // (Partially determined) path. ABT _path; diff --git a/src/mongo/db/query/optimizer/metadata.h b/src/mongo/db/query/optimizer/metadata.h index e67fdd5693c..70e1fd8689c 100644 --- a/src/mongo/db/query/optimizer/metadata.h +++ b/src/mongo/db/query/optimizer/metadata.h @@ -88,7 +88,7 @@ struct MultikeynessTrie { void merge(const MultikeynessTrie& other); void add(const ABT& path); - opt::unordered_map<std::string, MultikeynessTrie> children; + opt::unordered_map<FieldNameType, MultikeynessTrie, FieldNameType::Hasher> children; // An empty trie doesn't know whether anything is multikey. // 'true' means "not sure" while 'false' means "definitely no arrays". bool isMultiKey = true; diff --git a/src/mongo/db/query/optimizer/metadata_factory.cpp b/src/mongo/db/query/optimizer/metadata_factory.cpp index 36989c64129..6eadf67d1cc 100644 --- a/src/mongo/db/query/optimizer/metadata_factory.cpp +++ b/src/mongo/db/query/optimizer/metadata_factory.cpp @@ -70,7 +70,7 @@ ScanDefinition createScanDef(ScanDefOptions options, // Simplify partial filter requirements using the non-multikey paths. for (auto& [indexDefName, indexDef] : indexDefs) { [[maybe_unused]] const bool hasEmptyInterval = simplifyPartialSchemaReqPaths( - "" /*scanProjName*/, multikeynessTrie, indexDef.getPartialReqMap(), constFold); + "<root>", multikeynessTrie, indexDef.getPartialReqMap(), constFold); // If "hasEmptyInterval" is set, we have a partial filter index with an unsatisfiable // condition, which is thus guaranteed to never contain any documents. } diff --git a/src/mongo/db/query/optimizer/node.cpp b/src/mongo/db/query/optimizer/node.cpp index 78be197b709..901ef3fbfe2 100644 --- a/src/mongo/db/query/optimizer/node.cpp +++ b/src/mongo/db/query/optimizer/node.cpp @@ -79,11 +79,11 @@ static ProjectionNameVector extractProjectionNamesForScan( const FieldProjectionMap& fieldProjectionMap) { ProjectionNameVector result; - if (!fieldProjectionMap._ridProjection.empty()) { - result.push_back(fieldProjectionMap._ridProjection); + if (const auto& projName = fieldProjectionMap._ridProjection) { + result.push_back(*projName); } - if (!fieldProjectionMap._rootProjection.empty()) { - result.push_back(fieldProjectionMap._rootProjection); + if (const auto& projName = fieldProjectionMap._rootProjection) { + result.push_back(*projName); } for (const auto& entry : fieldProjectionMap._fieldProjections) { result.push_back(entry.second); @@ -278,7 +278,6 @@ EvaluationNode::EvaluationNode(ProjectionName projectionName, ProjectionType pro : Base(std::move(child), make<ExpressionBinder>(std::move(projectionName), std::move(projection))) { assertNodeSort(getChild()); - tassert(6684504, "Empty projection name", !getProjectionName().empty()); } bool EvaluationNode::operator==(const EvaluationNode& other) const { @@ -331,7 +330,7 @@ static ProjectionNameVector createSargableBindings(const PartialSchemaRequiremen static ProjectionNameVector createSargableReferences(const PartialSchemaRequirements& reqMap) { ProjectionNameOrderPreservingSet result; for (const auto& entry : reqMap) { - result.emplace_back(entry.first._projectionName); + result.emplace_back(*entry.first._projectionName); } return result.getVector(); } @@ -378,7 +377,7 @@ SargableNode::SargableNode(PartialSchemaRequirements reqMap, for (const auto& [key, req] : _reqMap) { tassert(6624088, "SargableNode cannot reference an internally bound projection", - boundsProjectionNameSet.count(key._projectionName) == 0); + boundsProjectionNameSet.count(*key._projectionName) == 0); } } diff --git a/src/mongo/db/query/optimizer/node.h b/src/mongo/db/query/optimizer/node.h index 894eeaeb353..cded5e129e9 100644 --- a/src/mongo/db/query/optimizer/node.h +++ b/src/mongo/db/query/optimizer/node.h @@ -844,11 +844,6 @@ public: private: properties::DistributionRequirement _distribution; - - /** - * Defined for hash and range-based partitioning. - */ - const ProjectionName _projectionName; }; /** diff --git a/src/mongo/db/query/optimizer/opt_phase_manager.cpp b/src/mongo/db/query/optimizer/opt_phase_manager.cpp index fdfd911dd5c..79e6b34201a 100644 --- a/src/mongo/db/query/optimizer/opt_phase_manager.cpp +++ b/src/mongo/db/query/optimizer/opt_phase_manager.cpp @@ -81,14 +81,17 @@ OptPhaseManager::OptPhaseManager(OptPhaseManager::PhaseSet phaseSet, } static std::string generateFreeVarsAssertMsg(const VariableEnvironment& env) { - std::string result; + str::stream os; + bool first = true; for (const auto& name : env.freeVariableNames()) { - if (!result.empty()) { - result += ", "; + if (first) { + first = false; + } else { + os << ", "; } - result += name; + os << name; } - return result; + return os; } template <OptPhase phase, class C> diff --git a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp index deea5286501..ff8977310a4 100644 --- a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp @@ -1529,7 +1529,7 @@ TEST(PhysRewriter, FilterIndexingStress) { os << "field" << index; result = make<FilterNode>( - make<EvalFilter>(make<PathGet>(os.str(), + make<EvalFilter>(make<PathGet>(FieldNameType{os.str()}, make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(0)), PathTraverse::kSingleLevel)), @@ -1916,12 +1916,12 @@ TEST(PhysRewriter, FilterReorder) { ProjectionName projName = prefixId.getNextId("field"); hints.emplace( PartialSchemaKey{"root", - make<PathGet>(projName, + make<PathGet>(FieldNameType{projName.value()}, make<PathTraverse>(make<PathIdentity>(), PathTraverse::kSingleLevel))}, kDefaultSelectivity * (kFilterCount - i)); result = make<FilterNode>( - make<EvalFilter>(make<PathGet>(std::move(projName), + make<EvalFilter>(make<PathGet>(FieldNameType{projName.value()}, make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(i)), PathTraverse::kSingleLevel)), diff --git a/src/mongo/db/query/optimizer/props.cpp b/src/mongo/db/query/optimizer/props.cpp index c0079a78ed2..07fd05ede30 100644 --- a/src/mongo/db/query/optimizer/props.cpp +++ b/src/mongo/db/query/optimizer/props.cpp @@ -358,7 +358,7 @@ size_t DistributionHash::operator()( size_t result = 0; updateHash(result, std::hash<DistributionType>()(distributionAndProjections._type)); for (const ProjectionName& projectionName : distributionAndProjections._projectionNames) { - updateHash(result, std::hash<ProjectionName>()(projectionName)); + updateHash(result, ProjectionName::Hasher()(projectionName)); } return result; } diff --git a/src/mongo/db/query/optimizer/reference_tracker.cpp b/src/mongo/db/query/optimizer/reference_tracker.cpp index a21aa06b25d..bf2dbbfac7d 100644 --- a/src/mongo/db/query/optimizer/reference_tracker.cpp +++ b/src/mongo/db/query/optimizer/reference_tracker.cpp @@ -35,7 +35,9 @@ namespace mongo::optimizer { struct CollectedInfo { - using VarRefsMap = opt::unordered_map<std::string, opt::unordered_map<const Variable*, bool>>; + using VarRefsMap = opt::unordered_map<ProjectionName, + opt::unordered_map<const Variable*, bool>, + ProjectionName::Hasher>; /** * All resolved variables so far, regardless of visibility in the ABT. @@ -52,7 +54,10 @@ struct CollectedInfo { * ABT. Maps from projection name to all Variable instances referencing that name. Variables * move from 'freeVars' to 'useMap' when they are resolved. */ - opt::unordered_map<std::string, std::vector<std::reference_wrapper<const Variable>>> freeVars; + opt::unordered_map<ProjectionName, + std::vector<std::reference_wrapper<const Variable>>, + ProjectionName::Hasher> + freeVars; /** * Maps from a node to the definitions (projections) available for use in its ancestor nodes. @@ -172,7 +177,7 @@ struct CollectedInfo { * Records collected last variable references for a specific variable. Should only be called * when the variable is guaranteed not to be referenced again in the ABT. */ - void finalizeLastRefs(const std::string& name) { + void finalizeLastRefs(const ProjectionName& name) { if (auto it = varLastRefs.find(name); it != varLastRefs.end()) { for (auto& [var, isLastRef] : it->second) { if (isLastRef) { @@ -742,15 +747,15 @@ bool VariableEnvironment::hasFreeVariables() const { return !_info->freeVars.empty(); } -opt::unordered_set<std::string> VariableEnvironment::freeVariableNames() const { - opt::unordered_set<std::string> freeVarNames; +ProjectionNameSet VariableEnvironment::freeVariableNames() const { + ProjectionNameSet freeVarNames; for (auto&& [name, vars] : _info->freeVars) { freeVarNames.insert(name); } return freeVarNames; } -size_t VariableEnvironment::freeOccurences(const std::string& variable) const { +size_t VariableEnvironment::freeOccurences(const ProjectionName& variable) const { auto it = _info->freeVars.find(variable); if (it == _info->freeVars.end()) { return 0; diff --git a/src/mongo/db/query/optimizer/reference_tracker.h b/src/mongo/db/query/optimizer/reference_tracker.h index 0b1425dec31..eb5016e7eec 100644 --- a/src/mongo/db/query/optimizer/reference_tracker.h +++ b/src/mongo/db/query/optimizer/reference_tracker.h @@ -53,7 +53,7 @@ struct Definition { }; struct CollectedInfo; -using DefinitionsMap = opt::unordered_map<ProjectionName, Definition>; +using DefinitionsMap = opt::unordered_map<ProjectionName, Definition, ProjectionName::Hasher>; struct VariableCollectorResult { // The Variables referenced by the subtree. @@ -62,7 +62,7 @@ struct VariableCollectorResult { // variable resolution. Tracking these separately allows us to easily check, for example, which // variables are referenced in but not defined by the subtree (i.e. variables which should be // defined elsewhere in the ABT). - opt::unordered_set<std::string> _definedVars; + ProjectionNameSet _definedVars; }; /** @@ -119,12 +119,12 @@ public: bool hasFreeVariables() const; - opt::unordered_set<std::string> freeVariableNames() const; + ProjectionNameSet freeVariableNames() const; /** * Returns the number of places in the ABT where there is a free Variable with name 'variable'. */ - size_t freeOccurences(const std::string& variable) const; + size_t freeOccurences(const ProjectionName& variable) const; /** * Returns whether 'var' is guaranteed to be the last access to the projection to which it diff --git a/src/mongo/db/query/optimizer/rewrites/path.cpp b/src/mongo/db/query/optimizer/rewrites/path.cpp index 83e5f6c45b5..fa03e569688 100644 --- a/src/mongo/db/query/optimizer/rewrites/path.cpp +++ b/src/mongo/db/query/optimizer/rewrites/path.cpp @@ -314,10 +314,10 @@ void PathFusion::tryFuseComposition(ABT& n, ABT& input) { // TODO: handle Drop elements. // Latest value per field. - opt::unordered_map<FieldNameType, ABT> fieldMap; + opt::unordered_map<FieldNameType, ABT, FieldNameType::Hasher> fieldMap; // Used to preserve the relative order in which fields are set on the result. FieldPathType orderedFieldNames; - boost::optional<std::set<FieldNameType>> toKeep; + boost::optional<FieldNameOrderedSet> toKeep; Type inputType = Type::any; if (auto constPtr = input.cast<Constant>(); constPtr != nullptr && constPtr->isObject()) { diff --git a/src/mongo/db/query/optimizer/rewrites/path_lower.cpp b/src/mongo/db/query/optimizer/rewrites/path_lower.cpp index d1cdf66c23d..0b21183f388 100644 --- a/src/mongo/db/query/optimizer/rewrites/path_lower.cpp +++ b/src/mongo/db/query/optimizer/rewrites/path_lower.cpp @@ -49,7 +49,7 @@ void EvalPathLowering::transport(ABT& n, const PathConstant&, ABT& c) { } void EvalPathLowering::transport(ABT& n, const PathIdentity&) { - const std::string& name = _prefixId.getNextId("x"); + const ProjectionName name{_prefixId.getNextId("x")}; n = make<LambdaAbstraction>(name, make<Variable>(name)); _changed = true; @@ -62,7 +62,7 @@ void EvalPathLowering::transport(ABT& n, const PathLambda&, ABT& lam) { void EvalPathLowering::transport(ABT& n, const PathDefault&, ABT& c) { // if (exists(x), x, c) - const std::string& name = _prefixId.getNextId("valDefault"); + const ProjectionName name{_prefixId.getNextId("valDefault")}; n = make<LambdaAbstraction>( name, @@ -77,26 +77,26 @@ void EvalPathLowering::transport(ABT& n, const PathCompare&, ABT& c) { } void EvalPathLowering::transport(ABT& n, const PathGet& p, ABT& inner) { - const std::string& name = _prefixId.getNextId("inputGet"); + const ProjectionName name{_prefixId.getNextId("inputGet")}; n = make<LambdaAbstraction>( name, make<LambdaApplication>( std::exchange(inner, make<Blackhole>()), make<FunctionCall>("getField", - makeSeq(make<Variable>(name), Constant::str(p.name()))))); + makeSeq(make<Variable>(name), Constant::str(p.name().value()))))); _changed = true; } void EvalPathLowering::transport(ABT& n, const PathDrop& drop) { // if (isObject(x), dropFields(x,...) , x) // Alternatively, we can implement a special builtin function that does the comparison and drop. - const std::string& name = _prefixId.getNextId("valDrop"); + const ProjectionName name{_prefixId.getNextId("valDrop")}; std::vector<ABT> params; params.emplace_back(make<Variable>(name)); for (const auto& fieldName : drop.getNames()) { - params.emplace_back(Constant::str(fieldName)); + params.emplace_back(Constant::str(fieldName.value())); } n = make<LambdaAbstraction>( @@ -110,12 +110,12 @@ void EvalPathLowering::transport(ABT& n, const PathDrop& drop) { void EvalPathLowering::transport(ABT& n, const PathKeep& keep) { // if (isObject(x), keepFields(x,...) , x) // Alternatively, we can implement a special builtin function that does the comparison and drop. - const std::string& name = _prefixId.getNextId("valKeep"); + const ProjectionName name{_prefixId.getNextId("valKeep")}; std::vector<ABT> params; params.emplace_back(make<Variable>(name)); for (const auto& fieldName : keep.getNames()) { - params.emplace_back(Constant::str(fieldName)); + params.emplace_back(Constant::str(fieldName.value())); } n = make<LambdaAbstraction>( @@ -128,7 +128,7 @@ void EvalPathLowering::transport(ABT& n, const PathKeep& keep) { void EvalPathLowering::transport(ABT& n, const PathObj&) { // if (isObject(x), x, Nothing) - const std::string& name = _prefixId.getNextId("valObj"); + const ProjectionName name{_prefixId.getNextId("valObj")}; n = make<LambdaAbstraction>( name, @@ -140,7 +140,7 @@ void EvalPathLowering::transport(ABT& n, const PathObj&) { void EvalPathLowering::transport(ABT& n, const PathArr&) { // if (isArray(x), x, Nothing) - const std::string& name = _prefixId.getNextId("valArr"); + const ProjectionName name{_prefixId.getNextId("valArr")}; n = make<LambdaAbstraction>( name, @@ -158,7 +158,7 @@ void EvalPathLowering::transport(ABT& n, const PathTraverse& p, ABT& inner) { "Currently we allow only multi-level traversal under EvalPath", p.getMaxDepth() == PathTraverse::kUnlimited); - const std::string& name = _prefixId.getNextId("valTraverse"); + const ProjectionName name{_prefixId.getNextId("valTraverse")}; n = make<LambdaAbstraction>(name, make<FunctionCall>("traverseP", @@ -169,8 +169,8 @@ void EvalPathLowering::transport(ABT& n, const PathTraverse& p, ABT& inner) { } void EvalPathLowering::transport(ABT& n, const PathField& p, ABT& inner) { - const std::string& name = _prefixId.getNextId("inputField"); - const std::string& val = _prefixId.getNextId("valField"); + const ProjectionName name{_prefixId.getNextId("inputField")}; + const ProjectionName val{_prefixId.getNextId("valField")}; n = make<LambdaAbstraction>( name, @@ -179,22 +179,22 @@ void EvalPathLowering::transport(ABT& n, const PathField& p, ABT& inner) { make<LambdaApplication>( std::exchange(inner, make<Blackhole>()), make<FunctionCall>("getField", - makeSeq(make<Variable>(name), Constant::str(p.name())))), - make<If>( - make<BinaryOp>(Operations::Or, - make<FunctionCall>("exists", makeSeq(make<Variable>(val))), - make<FunctionCall>("isObject", makeSeq(make<Variable>(name)))), - make<FunctionCall>( - "setField", - makeSeq(make<Variable>(name), Constant::str(p.name()), make<Variable>(val))), - make<Variable>(name)))); + makeSeq(make<Variable>(name), Constant::str(p.name().value())))), + make<If>(make<BinaryOp>(Operations::Or, + make<FunctionCall>("exists", makeSeq(make<Variable>(val))), + make<FunctionCall>("isObject", makeSeq(make<Variable>(name)))), + make<FunctionCall>("setField", + makeSeq(make<Variable>(name), + Constant::str(p.name().value()), + make<Variable>(val))), + make<Variable>(name)))); _changed = true; } void EvalPathLowering::transport(ABT& n, const PathComposeM&, ABT& p1, ABT& p2) { // p1 * p2 -> (p2 (p1 input)) - const std::string& name = _prefixId.getNextId("inputComposeM"); + const ProjectionName name{_prefixId.getNextId("inputComposeM")}; n = make<LambdaAbstraction>( name, @@ -251,7 +251,7 @@ void EvalFilterLowering::transport(ABT& n, const PathLambda&, ABT& lam) { } void EvalFilterLowering::transport(ABT& n, const PathDefault&, ABT& c) { - const std::string& name = _prefixId.getNextId("valDefault"); + const ProjectionName name{_prefixId.getNextId("valDefault")}; n = make<LambdaAbstraction>( name, @@ -263,7 +263,7 @@ void EvalFilterLowering::transport(ABT& n, const PathDefault&, ABT& c) { } void EvalFilterLowering::transport(ABT& n, const PathCompare& cmp, ABT& c) { - const std::string& name = _prefixId.getNextId("valCmp"); + const ProjectionName name{_prefixId.getNextId("valCmp")}; if (cmp.op() == Operations::Eq) { n = make<LambdaAbstraction>( @@ -289,16 +289,16 @@ void EvalFilterLowering::transport(ABT& n, const PathCompare& cmp, ABT& c) { } void EvalFilterLowering::transport(ABT& n, const PathGet& p, ABT& inner) { - const std::string& name = _prefixId.getNextId("inputGet"); + const ProjectionName name{_prefixId.getNextId("inputGet")}; int idx; - bool isNumber = NumberParser{}(p.name(), &idx).isOK(); + bool isNumber = NumberParser{}(p.name().value(), &idx).isOK(); n = make<LambdaAbstraction>( name, make<LambdaApplication>( std::exchange(inner, make<Blackhole>()), make<FunctionCall>(isNumber ? "getFieldOrElement" : "getField", - makeSeq(make<Variable>(name), Constant::str(p.name()))))); + makeSeq(make<Variable>(name), Constant::str(p.name().value()))))); _changed = true; } @@ -311,14 +311,14 @@ void EvalFilterLowering::transport(ABT& n, const PathKeep& keep) { } void EvalFilterLowering::transport(ABT& n, const PathObj&) { - const std::string& name = _prefixId.getNextId("valObj"); + const ProjectionName name{_prefixId.getNextId("valObj")}; n = make<LambdaAbstraction>(name, make<FunctionCall>("isObject", makeSeq(make<Variable>(name)))); _changed = true; } void EvalFilterLowering::transport(ABT& n, const PathArr&) { - const std::string& name = _prefixId.getNextId("valArr"); + const ProjectionName name{_prefixId.getNextId("valArr")}; n = make<LambdaAbstraction>(name, make<FunctionCall>("isArray", makeSeq(make<Variable>(name)))); _changed = true; } @@ -327,7 +327,7 @@ void EvalFilterLowering::prepare(ABT& n, const PathTraverse& t) { int idx; // This is a bad hack that detect if a child is number path element if (auto child = t.getPath().cast<PathGet>(); - child && NumberParser{}(child->name(), &idx).isOK()) { + child && NumberParser{}(child->name().value(), &idx).isOK()) { _traverseStack.emplace_back(n.ref()); } } @@ -339,7 +339,7 @@ void EvalFilterLowering::transport(ABT& n, const PathTraverse& p, ABT& inner) { "Currently we allow only single-level traversal under EvalFilter", p.getMaxDepth() == PathTraverse::kSingleLevel); - const std::string& name = _prefixId.getNextId("valTraverse"); + const ProjectionName name{_prefixId.getNextId("valTraverse")}; ABT numberPath = Constant::boolean(false); if (!_traverseStack.empty() && _traverseStack.back() == n.ref()) { @@ -360,7 +360,7 @@ void EvalFilterLowering::transport(ABT& n, const PathField& p, ABT& inner) { } void EvalFilterLowering::transport(ABT& n, const PathComposeM&, ABT& p1, ABT& p2) { - const std::string& name = _prefixId.getNextId("inputComposeM"); + const ProjectionName name{_prefixId.getNextId("inputComposeM")}; n = make<LambdaAbstraction>( name, @@ -376,7 +376,7 @@ void EvalFilterLowering::transport(ABT& n, const PathComposeM&, ABT& p1, ABT& p2 } void EvalFilterLowering::transport(ABT& n, const PathComposeA&, ABT& p1, ABT& p2) { - const std::string& name = _prefixId.getNextId("inputComposeA"); + const ProjectionName name{_prefixId.getNextId("inputComposeA")}; n = make<LambdaAbstraction>( name, diff --git a/src/mongo/db/query/optimizer/rewrites/path_optimizer_test.cpp b/src/mongo/db/query/optimizer/rewrites/path_optimizer_test.cpp index 6a04c6648f8..5d5a37b453a 100644 --- a/src/mongo/db/query/optimizer/rewrites/path_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/rewrites/path_optimizer_test.cpp @@ -410,7 +410,7 @@ TEST(Path, Fuse5) { auto project = make<EvaluationNode>( "x", - make<EvalPath>(make<PathKeep>(PathKeep::NameSet{"a", "b", "c"}), make<Variable>("root")), + make<EvalPath>(make<PathKeep>(FieldNameOrderedSet{"a", "b", "c"}), make<Variable>("root")), std::move(scanNode)); // Get "a" Traverse Compare= 2 @@ -494,11 +494,11 @@ TEST(Path, Fuse6) { auto project = make<EvaluationNode>( "x", - make<EvalPath>( - make<PathComposeM>(make<PathComposeM>(make<PathObj>(), - make<PathKeep>(PathKeep::NameSet{"a", "b", "c"})), - make<PathField>("a", make<PathConstant>(Constant::emptyObject()))), - make<Variable>("root")), + make<EvalPath>(make<PathComposeM>( + make<PathComposeM>(make<PathObj>(), + make<PathKeep>(FieldNameOrderedSet{"a", "b", "c"})), + make<PathField>("a", make<PathConstant>(Constant::emptyObject()))), + make<Variable>("root")), std::move(scanNode)); auto tree = make<RootNode>(properties::ProjectionRequirement{ProjectionNameVector{"x"}}, @@ -576,7 +576,7 @@ TEST(Path, Fuse7) { "py", make<EvalPath>( make<PathComposeM>( - make<PathComposeM>(make<PathKeep>(PathKeep::NameSet{"a"}), + make<PathComposeM>(make<PathKeep>(FieldNameOrderedSet{"a"}), make<PathField>("a", make<PathConstant>(make<Variable>("px")))), make<PathDefault>(Constant::emptyObject())), make<Variable>("root")), @@ -873,9 +873,9 @@ TEST(Path, ProjElim2) { TEST(Path, ProjElim3) { auto node = make<ScanNode>("root", "test"); - std::string var = "root"; + ProjectionName var{"root"}; for (int i = 0; i < 100; ++i) { - std::string newVar = "p" + std::to_string(i); + ProjectionName newVar{"p" + std::to_string(i)}; node = make<EvaluationNode>( newVar, // make<FunctionCall>("anyFunctionWillDo", makeSeq(make<Variable>(var))), diff --git a/src/mongo/db/query/optimizer/syntax/expr.cpp b/src/mongo/db/query/optimizer/syntax/expr.cpp index 94b8b771ef2..3b340381cb6 100644 --- a/src/mongo/db/query/optimizer/syntax/expr.cpp +++ b/src/mongo/db/query/optimizer/syntax/expr.cpp @@ -57,7 +57,7 @@ ABT Constant::createFromCopy(const sbe::value::TypeTags tag, const sbe::value::V return make<Constant>(copy.first, copy.second); } -ABT Constant::str(std::string str) { +ABT Constant::str(StringData str) { // Views are non-owning so we have to make a copy. auto [tag, val] = makeNewString(str); return make<Constant>(tag, val); diff --git a/src/mongo/db/query/optimizer/syntax/expr.h b/src/mongo/db/query/optimizer/syntax/expr.h index 10046f183e0..9d7fd18b181 100644 --- a/src/mongo/db/query/optimizer/syntax/expr.h +++ b/src/mongo/db/query/optimizer/syntax/expr.h @@ -51,7 +51,7 @@ public: static ABT createFromCopy(sbe::value::TypeTags tag, sbe::value::Value val); - static ABT str(std::string str); + static ABT str(StringData str); static ABT int32(int32_t valueInt32); static ABT int64(int64_t valueInt64); @@ -129,12 +129,10 @@ private: * elsewhere. */ class Variable final : public Operator<0>, public ExpressionSyntaxSort { - std::string _name; + ProjectionName _name; public: - Variable(std::string inName) : _name(std::move(inName)) { - tassert(6684503, "Empty variable name", !_name.empty()); - } + Variable(ProjectionName inName) : _name(std::move(inName)) {} bool operator==(const Variable& other) const { return _name == other._name; @@ -244,10 +242,10 @@ public: class Let final : public Operator<2>, public ExpressionSyntaxSort { using Base = Operator<2>; - std::string _varName; + ProjectionName _varName; public: - Let(std::string var, ABT inBind, ABT inExpr) + Let(ProjectionName var, ABT inBind, ABT inExpr) : Base(std::move(inBind), std::move(inExpr)), _varName(std::move(var)) { assertExprSort(bind()); assertExprSort(in()); @@ -278,10 +276,10 @@ public: class LambdaAbstraction final : public Operator<1>, public ExpressionSyntaxSort { using Base = Operator<1>; - std::string _varName; + ProjectionName _varName; public: - LambdaAbstraction(std::string var, ABT inBody) + LambdaAbstraction(ProjectionName var, ABT inBody) : Base(std::move(inBody)), _varName(std::move(var)) { assertExprSort(getBody()); } diff --git a/src/mongo/db/query/optimizer/syntax/path.h b/src/mongo/db/query/optimizer/syntax/path.h index 84f219afa19..e949f48216c 100644 --- a/src/mongo/db/query/optimizer/syntax/path.h +++ b/src/mongo/db/query/optimizer/syntax/path.h @@ -169,20 +169,18 @@ public: */ class PathDrop final : public Operator<0>, public PathSyntaxSort { public: - using NameSet = std::set<std::string>; - - PathDrop(NameSet inNames) : _names(std::move(inNames)) {} + PathDrop(FieldNameOrderedSet inNames) : _names(std::move(inNames)) {} bool operator==(const PathDrop& other) const { return _names == other._names; } - const NameSet& getNames() const { + const auto& getNames() const { return _names; } private: - const NameSet _names; + const FieldNameOrderedSet _names; }; /** @@ -193,20 +191,18 @@ private: */ class PathKeep final : public Operator<0>, public PathSyntaxSort { public: - using NameSet = std::set<std::string>; - - PathKeep(NameSet inNames) : _names(std::move(inNames)) {} + PathKeep(FieldNameOrderedSet inNames) : _names(std::move(inNames)) {} bool operator==(const PathKeep other) const { return _names == other._names; } - const NameSet& getNames() const { + const auto& getNames() const { return _names; } private: - const NameSet _names; + const FieldNameOrderedSet _names; }; /** @@ -290,10 +286,11 @@ private: */ class PathField final : public Operator<1>, public PathSyntaxSort { using Base = Operator<1>; - std::string _name; + FieldNameType _name; public: - PathField(std::string inName, ABT inPath) : Base(std::move(inPath)), _name(std::move(inName)) { + PathField(FieldNameType inName, ABT inPath) + : Base(std::move(inPath)), _name(std::move(inName)) { assertPathSort(getPath()); } @@ -323,10 +320,10 @@ public: */ class PathGet final : public Operator<1>, public PathSyntaxSort { using Base = Operator<1>; - std::string _name; + FieldNameType _name; public: - PathGet(std::string inName, ABT inPath) : Base(std::move(inPath)), _name(std::move(inName)) { + PathGet(FieldNameType inName, ABT inPath) : Base(std::move(inPath)), _name(std::move(inName)) { assertPathSort(getPath()); } diff --git a/src/mongo/db/query/optimizer/syntax/syntax.h b/src/mongo/db/query/optimizer/syntax/syntax.h index 03f35718871..3c1d23d590e 100644 --- a/src/mongo/db/query/optimizer/syntax/syntax.h +++ b/src/mongo/db/query/optimizer/syntax/syntax.h @@ -33,6 +33,7 @@ #include "mongo/db/query/optimizer/algebra/operator.h" #include "mongo/db/query/optimizer/algebra/polyvalue.h" +#include "mongo/db/query/optimizer/defs.h" #include "mongo/db/query/optimizer/syntax/syntax_fwd_declare.h" #include "mongo/db/query/optimizer/utils/printable_enum.h" #include "mongo/util/assert_util.h" @@ -236,7 +237,7 @@ public: /** * Construct Variable objects out of provided vector of strings. */ - References(const std::vector<std::string>& names) : Base(ABTVector{}) { + References(const ProjectionNameVector& names) : Base(ABTVector{}) { // Construct actual Variable objects from names and make them the children of this object. for (const auto& name : names) { nodes().emplace_back(make<Variable>(name)); @@ -266,17 +267,17 @@ public: */ class ExpressionBinder : public OperatorDynamicHomogenous { using Base = OperatorDynamicHomogenous; - std::vector<std::string> _names; + ProjectionNameVector _names; public: - ExpressionBinder(std::string name, ABT expr) : Base(makeSeq(std::move(expr))) { + ExpressionBinder(ProjectionName name, ABT expr) : Base(makeSeq(std::move(expr))) { _names.emplace_back(std::move(name)); for (const auto& node : nodes()) { assertExprSort(node); } } - ExpressionBinder(std::vector<std::string> names, ABTVector exprs) + ExpressionBinder(ProjectionNameVector names, ABTVector exprs) : Base(std::move(exprs)), _names(std::move(names)) { for (const auto& node : nodes()) { assertExprSort(node); @@ -287,7 +288,7 @@ public: return _names == other._names && exprs() == other.exprs(); } - const std::vector<std::string>& names() const { + const ProjectionNameVector& names() const { return _names; } diff --git a/src/mongo/db/query/optimizer/utils/abt_compare.cpp b/src/mongo/db/query/optimizer/utils/abt_compare.cpp index 180af41c6d0..77151482d2f 100644 --- a/src/mongo/db/query/optimizer/utils/abt_compare.cpp +++ b/src/mongo/db/query/optimizer/utils/abt_compare.cpp @@ -56,7 +56,11 @@ int compareContainers(const T& n1, const T& n2, const Fn& fn) { return 0; } -int compareStrings(const std::string& v1, const std::string& v2) { +/** + * Used to compare strings and strong string aliases. + */ +template <class T> +int compareStrings(const T& v1, const T& v2) { return v1.compare(v2); } @@ -220,11 +224,11 @@ private: } int compare(const PathDrop& node, const PathDrop& other) { - return compareContainers(node.getNames(), other.getNames(), compareStrings); + return compareContainers(node.getNames(), other.getNames(), compareStrings<FieldNameType>); } int compare(const PathKeep& node, const PathKeep& other) { - return compareContainers(node.getNames(), other.getNames(), compareStrings); + return compareContainers(node.getNames(), other.getNames(), compareStrings<FieldNameType>); } int compare(const PathObj& node, const PathObj& other) { @@ -277,7 +281,8 @@ private: } int compare(const ExpressionBinder& node, const ExpressionBinder& other) { - const int cmp = compareContainers(node.names(), other.names(), compareStrings); + const int cmp = + compareContainers(node.names(), other.names(), compareStrings<ProjectionName>); if (cmp != 0) { return cmp; } diff --git a/src/mongo/db/query/optimizer/utils/abt_hash.cpp b/src/mongo/db/query/optimizer/utils/abt_hash.cpp index b9f8d5a2517..c447d4b6458 100644 --- a/src/mongo/db/query/optimizer/utils/abt_hash.cpp +++ b/src/mongo/db/query/optimizer/utils/abt_hash.cpp @@ -37,7 +37,7 @@ namespace mongo::optimizer { static size_t computeCollationHash(const properties::CollationRequirement& prop) { size_t collationHash = 17; for (const auto& entry : prop.getCollationSpec()) { - updateHash(collationHash, std::hash<ProjectionName>()(entry.first)); + updateHash(collationHash, ProjectionName::Hasher()(entry.first)); updateHash(collationHash, std::hash<CollationOp>()(entry.second)); } return collationHash; @@ -53,7 +53,7 @@ static size_t computeLimitSkipHash(const properties::LimitSkipRequirement& prop) static size_t computePropertyProjectionsHash(const ProjectionNameVector& projections) { size_t resultHash = 17; for (const ProjectionName& projection : projections) { - updateHashUnordered(resultHash, std::hash<ProjectionName>()(projection)); + updateHashUnordered(resultHash, ProjectionName::Hasher()(projection)); } return resultHash; } @@ -123,9 +123,14 @@ static size_t computePartialSchemaReqHash(const PartialSchemaRequirements& reqMa IntervalHasher<IntervalReqExpr> intervalHasher; for (const auto& [key, req] : reqMap) { - updateHash(result, std::hash<ProjectionName>()(key._projectionName)); + if (const auto& projName = key._projectionName) { + updateHash(result, ProjectionName::Hasher()(*projName)); + } updateHash(result, ABTHashGenerator::generate(key._path)); - updateHash(result, std::hash<ProjectionName>()(req.getBoundProjectionName().value_or(""))); + + if (const auto& projName = req.getBoundProjectionName()) { + updateHash(result, ProjectionName::Hasher()(*projName)); + } updateHash(result, intervalHasher.compute(req.getIntervals())); updateHash(result, std::hash<bool>()(req.getIsPerfOnly())); } @@ -152,7 +157,9 @@ public: } size_t transport(const ExpressionBinder& binders, std::vector<size_t> inResults) { - return computeHashSeq<2>(computeVectorHash(binders.names()), computeVectorHash(inResults)); + return computeHashSeq<2>( + computeVectorHash<ProjectionName, ProjectionName::Hasher>(binders.names()), + computeVectorHash(inResults)); } size_t transport(const ScanNode& node, size_t bindResult) { @@ -194,7 +201,7 @@ public: size_t leftChildResult, size_t rightChildResult) { // Specifically always including children. - return computeHashSeq<45>(std::hash<ProjectionName>()(node.getScanProjectionName()), + return computeHashSeq<45>(ProjectionName::Hasher()(node.getScanProjectionName()), leftChildResult, rightChildResult); } @@ -267,7 +274,7 @@ public: } size_t transport(const Variable& expr) { - return computeHashSeq<18>(std::hash<std::string>()(expr.name())); + return computeHashSeq<18>(ProjectionName::Hasher()(expr.name())); } size_t transport(const UnaryOp& expr, size_t inResult) { @@ -283,11 +290,11 @@ public: } size_t transport(const Let& expr, size_t bindResult, size_t exprResult) { - return computeHashSeq<22>(std::hash<std::string>()(expr.varName()), bindResult, exprResult); + return computeHashSeq<22>(ProjectionName::Hasher()(expr.varName()), bindResult, exprResult); } size_t transport(const LambdaAbstraction& expr, size_t inResult) { - return computeHashSeq<23>(std::hash<std::string>()(expr.varName()), inResult); + return computeHashSeq<23>(ProjectionName::Hasher()(expr.varName()), inResult); } size_t transport(const LambdaApplication& expr, size_t lambdaResult, size_t argumentResult) { @@ -336,16 +343,16 @@ public: size_t transport(const PathDrop& path) { size_t namesHash = 17; - for (const std::string& name : path.getNames()) { - updateHash(namesHash, std::hash<std::string>()(name)); + for (const FieldNameType& name : path.getNames()) { + updateHash(namesHash, FieldNameType::Hasher()(name)); } return computeHashSeq<34>(namesHash); } size_t transport(const PathKeep& path) { size_t namesHash = 17; - for (const std::string& name : path.getNames()) { - updateHash(namesHash, std::hash<std::string>()(name)); + for (const FieldNameType& name : path.getNames()) { + updateHash(namesHash, FieldNameType::Hasher()(name)); } return computeHashSeq<35>(namesHash); } @@ -363,11 +370,11 @@ public: } size_t transport(const PathField& path, size_t inResult) { - return computeHashSeq<39>(std::hash<std::string>()(path.name()), inResult); + return computeHashSeq<39>(FieldNameType::Hasher()(path.name()), inResult); } size_t transport(const PathGet& path, size_t inResult) { - return computeHashSeq<40>(std::hash<std::string>()(path.name()), inResult); + return computeHashSeq<40>(FieldNameType::Hasher()(path.name()), inResult); } size_t transport(const PathComposeM& path, size_t leftResult, size_t rightResult) { diff --git a/src/mongo/db/query/optimizer/utils/reftracker_utils.cpp b/src/mongo/db/query/optimizer/utils/reftracker_utils.cpp index 2a6fb284dd0..18b0fb020b4 100644 --- a/src/mongo/db/query/optimizer/utils/reftracker_utils.cpp +++ b/src/mongo/db/query/optimizer/utils/reftracker_utils.cpp @@ -41,142 +41,140 @@ namespace mongo::optimizer { class NodeVariableTracker { public: template <typename T, typename... Ts> - VariableNameSetType walk(const T&, Ts&&...) { + ProjectionNameSet walk(const T&, Ts&&...) { static_assert(!std::is_base_of_v<Node, T>, "Nodes must implement variable tracking"); // Default case: no variables. return {}; } - VariableNameSetType walk(const ScanNode& /*node*/, const ABT& /*binds*/) { + ProjectionNameSet walk(const ScanNode& /*node*/, const ABT& /*binds*/) { return {}; } - VariableNameSetType walk(const ValueScanNode& /*node*/, const ABT& /*binds*/) { + ProjectionNameSet walk(const ValueScanNode& /*node*/, const ABT& /*binds*/) { return {}; } - VariableNameSetType walk(const PhysicalScanNode& /*node*/, const ABT& /*binds*/) { + ProjectionNameSet walk(const PhysicalScanNode& /*node*/, const ABT& /*binds*/) { return {}; } - VariableNameSetType walk(const CoScanNode& /*node*/) { + ProjectionNameSet walk(const CoScanNode& /*node*/) { return {}; } - VariableNameSetType walk(const IndexScanNode& /*node*/, const ABT& /*binds*/) { + ProjectionNameSet walk(const IndexScanNode& /*node*/, const ABT& /*binds*/) { return {}; } - VariableNameSetType walk(const SeekNode& /*node*/, const ABT& /*binds*/, const ABT& refs) { + ProjectionNameSet walk(const SeekNode& /*node*/, const ABT& /*binds*/, const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const MemoLogicalDelegatorNode& /*node*/) { + ProjectionNameSet walk(const MemoLogicalDelegatorNode& /*node*/) { return {}; } - VariableNameSetType walk(const MemoPhysicalDelegatorNode& /*node*/) { + ProjectionNameSet walk(const MemoPhysicalDelegatorNode& /*node*/) { return {}; } - VariableNameSetType walk(const FilterNode& /*node*/, const ABT& /*child*/, const ABT& expr) { + ProjectionNameSet walk(const FilterNode& /*node*/, const ABT& /*child*/, const ABT& expr) { return extractFromABT(expr); } - VariableNameSetType walk(const EvaluationNode& /*node*/, - const ABT& /*child*/, - const ABT& expr) { + ProjectionNameSet walk(const EvaluationNode& /*node*/, const ABT& /*child*/, const ABT& expr) { return extractFromABT(expr); } - VariableNameSetType walk(const SargableNode& /*node*/, - const ABT& /*child*/, - const ABT& /*binds*/, - const ABT& refs) { + ProjectionNameSet walk(const SargableNode& /*node*/, + const ABT& /*child*/, + const ABT& /*binds*/, + const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const RIDIntersectNode& /*node*/, - const ABT& /*leftChild*/, - const ABT& /*rightChild*/) { + ProjectionNameSet walk(const RIDIntersectNode& /*node*/, + const ABT& /*leftChild*/, + const ABT& /*rightChild*/) { return {}; } - VariableNameSetType walk(const BinaryJoinNode& /*node*/, - const ABT& /*leftChild*/, - const ABT& /*rightChild*/, - const ABT& expr) { + ProjectionNameSet walk(const BinaryJoinNode& /*node*/, + const ABT& /*leftChild*/, + const ABT& /*rightChild*/, + const ABT& expr) { return extractFromABT(expr); } - VariableNameSetType walk(const HashJoinNode& /*node*/, - const ABT& /*leftChild*/, - const ABT& /*rightChild*/, - const ABT& refs) { + ProjectionNameSet walk(const HashJoinNode& /*node*/, + const ABT& /*leftChild*/, + const ABT& /*rightChild*/, + const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const MergeJoinNode& /*node*/, - const ABT& /*leftChild*/, - const ABT& /*rightChild*/, - const ABT& refs) { + ProjectionNameSet walk(const MergeJoinNode& /*node*/, + const ABT& /*leftChild*/, + const ABT& /*rightChild*/, + const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const UnionNode& /*node*/, - const ABTVector& /*children*/, - const ABT& /*binder*/, - const ABT& refs) { + ProjectionNameSet walk(const UnionNode& /*node*/, + const ABTVector& /*children*/, + const ABT& /*binder*/, + const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const GroupByNode& /*node*/, - const ABT& /*child*/, - const ABT& /*aggBinder*/, - const ABT& aggRefs, - const ABT& /*groupbyBinder*/, - const ABT& groupbyRefs) { - VariableNameSetType result; + ProjectionNameSet walk(const GroupByNode& /*node*/, + const ABT& /*child*/, + const ABT& /*aggBinder*/, + const ABT& aggRefs, + const ABT& /*groupbyBinder*/, + const ABT& groupbyRefs) { + ProjectionNameSet result; extractFromABT(result, aggRefs); extractFromABT(result, groupbyRefs); return result; } - VariableNameSetType walk(const UnwindNode& /*node*/, - const ABT& /*child*/, - const ABT& /*binds*/, - const ABT& refs) { + ProjectionNameSet walk(const UnwindNode& /*node*/, + const ABT& /*child*/, + const ABT& /*binds*/, + const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const UniqueNode& /*node*/, const ABT& /*child*/, const ABT& refs) { + ProjectionNameSet walk(const UniqueNode& /*node*/, const ABT& /*child*/, const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const CollationNode& /*node*/, const ABT& /*child*/, const ABT& refs) { + ProjectionNameSet walk(const CollationNode& /*node*/, const ABT& /*child*/, const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const LimitSkipNode& /*node*/, const ABT& /*child*/) { + ProjectionNameSet walk(const LimitSkipNode& /*node*/, const ABT& /*child*/) { return {}; } - VariableNameSetType walk(const ExchangeNode& /*node*/, const ABT& /*child*/, const ABT& refs) { + ProjectionNameSet walk(const ExchangeNode& /*node*/, const ABT& /*child*/, const ABT& refs) { return extractFromABT(refs); } - VariableNameSetType walk(const RootNode& /*node*/, const ABT& /*child*/, const ABT& refs) { + ProjectionNameSet walk(const RootNode& /*node*/, const ABT& /*child*/, const ABT& refs) { return extractFromABT(refs); } - static VariableNameSetType collect(const ABT& n) { + static ProjectionNameSet collect(const ABT& n) { NodeVariableTracker tracker; return algebra::walk<false>(n, tracker); } private: - void extractFromABT(VariableNameSetType& vars, const ABT& v) { + void extractFromABT(ProjectionNameSet& vars, const ABT& v) { const auto& result = VariableEnvironment::getVariables(v); for (const Variable& var : result._variables) { if (result._definedVars.count(var.name()) == 0) { @@ -186,14 +184,14 @@ private: } } - VariableNameSetType extractFromABT(const ABT& v) { - VariableNameSetType result; + ProjectionNameSet extractFromABT(const ABT& v) { + ProjectionNameSet result; extractFromABT(result, v); return result; } }; -VariableNameSetType collectVariableReferences(const ABT& n) { +ProjectionNameSet collectVariableReferences(const ABT& n) { return NodeVariableTracker::collect(n); } diff --git a/src/mongo/db/query/optimizer/utils/reftracker_utils.h b/src/mongo/db/query/optimizer/utils/reftracker_utils.h index 7e0ab3a64f2..6cf7746a99a 100644 --- a/src/mongo/db/query/optimizer/utils/reftracker_utils.h +++ b/src/mongo/db/query/optimizer/utils/reftracker_utils.h @@ -37,7 +37,6 @@ namespace mongo::optimizer { /** * Used to extract variable references from a node. */ -using VariableNameSetType = opt::unordered_set<std::string>; -VariableNameSetType collectVariableReferences(const ABT& n); +ProjectionNameSet collectVariableReferences(const ABT& n); } // namespace mongo::optimizer diff --git a/src/mongo/db/query/optimizer/utils/strong_string_alias.h b/src/mongo/db/query/optimizer/utils/strong_string_alias.h new file mode 100644 index 00000000000..e78dd83e60d --- /dev/null +++ b/src/mongo/db/query/optimizer/utils/strong_string_alias.h @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2022-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. + */ + +#pragma once + +#include "mongo/util/assert_util.h" +#include "mongo/util/str.h" + + +namespace mongo::optimizer { + +/** + * Strong string alias. It is used to provide strong type safety between various string types in the + * optimizer code. Instances with different tags are not constructable from, assignable to and + * comparable to each other. TagType needs to define a constexpr boolean "kAllowEmpty" which + * determines if empty strings are allowed. + */ +template <class TagType> +class StrongStringAlias { +public: + struct Hasher { + size_t operator()(const StrongStringAlias& v) const { + return std::hash<std::string>()(v._value); + } + }; + + // Allow implicit construction from a string literal, but not from a const char*. + template <size_t N> + StrongStringAlias(const char (&value)[N]) : _value(value){}; + + // We disallow empty strings based on the tag's kAllowEmpty field. + template <class TagType1 = TagType, + class = typename std::enable_if<!TagType1::kAllowEmpty>::type> + StrongStringAlias(const char (&value)[1]) = delete; + + // Need to explicitly construct from StringData and const char*. + explicit StrongStringAlias(StringData value) : _value(value) { + if constexpr (!TagType::kAllowEmpty) { + invariant(!_value.empty()); + } + } + + StrongStringAlias(const StrongStringAlias<TagType>& other) = default; + StrongStringAlias(StrongStringAlias<TagType>&& other) = default; + StrongStringAlias& operator=(const StrongStringAlias& other) = default; + StrongStringAlias& operator=(StrongStringAlias&& other) = default; + + bool operator==(const StrongStringAlias& other) const { + return _value == other._value; + } + bool operator!=(const StrongStringAlias& other) const { + return _value != other._value; + } + + bool operator<(const StrongStringAlias& other) const { + return _value < other._value; + } + + int compare(const StrongStringAlias& other) const { + return _value.compare(other._value); + } + + template <size_t N> + bool operator==(const char (&value)[N]) const { + return _value == value; + } + template <size_t N> + bool operator!=(const char (&value)[N]) const { + return _value != value; + } + + StringData value() const { + return _value; + } + +private: + std::string _value; +}; + +/** + * Exclude str::stream since it currently leads to ambiguous calls. + */ +template <class StreamType, + class TagType, + class = typename std::enable_if<!std::is_same<StreamType, str::stream>::value>::type> +StreamType& operator<<(StreamType& stream, const StrongStringAlias<TagType>& t) { + return stream << t.value(); +} + +} // namespace mongo::optimizer diff --git a/src/mongo/db/query/optimizer/utils/utils.cpp b/src/mongo/db/query/optimizer/utils/utils.cpp index d09aea3cbf4..84c1c4bcbb6 100644 --- a/src/mongo/db/query/optimizer/utils/utils.cpp +++ b/src/mongo/db/query/optimizer/utils/utils.cpp @@ -60,10 +60,10 @@ bool isSimplePath(const ABT& node) { return false; } -std::string PrefixId::getNextId(const std::string& key) { +ProjectionName PrefixId::getNextId(const StringData& prefix) { std::ostringstream os; - os << key << "_" << _idCounterPerKey[key]++; - return os.str(); + os << prefix << "_" << _idCounterPerPrefix[prefix.toString()]++; + return ProjectionName{os.str()}; } ProjectionNameOrderedSet convertToOrderedSet(ProjectionNameSet unordered) { @@ -144,7 +144,7 @@ bool areCompoundIntervalsEqualities(const CompoundIntervalRequirement& intervals return true; } -CollationSplitResult splitCollationSpec(const ProjectionName& ridProjName, +CollationSplitResult splitCollationSpec(const boost::optional<ProjectionName>& ridProjName, const ProjectionCollationSpec& collationSpec, const ProjectionNameSet& leftProjections, const ProjectionNameSet& rightProjections) { @@ -220,7 +220,7 @@ public: PartialSchemaRequirements newMap; for (auto& [key, req] : pathResult->_reqMap) { - if (!key._projectionName.empty()) { + if (key._projectionName) { return {}; } newMap.emplace(PartialSchemaKey{boundVarName, key._path}, std::move(req)); @@ -433,7 +433,7 @@ public: auto intervalExpr = IntervalReqExpr::makeSingularDNF(IntervalRequirement{ {true /*inclusive*/, Constant::null()}, {true /*inclusive*/, Constant::null()}}); return {{PartialSchemaRequirements{ - {PartialSchemaKey{"" /*projectionName*/, make<PathIdentity>()}, + {PartialSchemaKey{make<PathIdentity>()}, PartialSchemaRequirement{boost::none /*boundProjectionName*/, std::move(intervalExpr), false /*isPerfOnly*/}}}}}; @@ -456,7 +456,7 @@ public: PartialSchemaRequirements newMap; for (auto& entry : inputResult->_reqMap) { - if (!entry.first._projectionName.empty()) { + if (entry.first._projectionName) { return {}; } @@ -467,7 +467,7 @@ public: std::swap(appendedPath.cast<T>()->getPath(), path); std::swap(path, appendedPath); - newMap.emplace(PartialSchemaKey{"", std::move(path)}, std::move(entry.second)); + newMap.emplace(PartialSchemaKey{std::move(path)}, std::move(entry.second)); } inputResult->_reqMap = std::move(newMap); @@ -533,7 +533,7 @@ public: } return {{PartialSchemaRequirements{ - {PartialSchemaKey{"" /*projectionName*/, make<PathIdentity>()}, + {PartialSchemaKey{make<PathIdentity>()}, PartialSchemaRequirement{boost::none /*boundProjectionName*/, std::move(*unionedInterval), false /*isPerfOnly*/}}}}}; @@ -583,14 +583,14 @@ public: auto intervalExpr = IntervalReqExpr::makeSingularDNF(IntervalRequirement{ {lowBoundInclusive, std::move(lowBound)}, {highBoundInclusive, std::move(highBound)}}); return {{PartialSchemaRequirements{ - {PartialSchemaKey{"" /*projectionName*/, make<PathIdentity>()}, + {PartialSchemaKey{make<PathIdentity>()}, PartialSchemaRequirement{boost::none /*boundProjectionName*/, std::move(intervalExpr), false /*isPerfOnly*/}}}}}; } ResultType transport(const ABT& n, const PathIdentity& pathIdentity) { - return {{PartialSchemaRequirements{{{"" /*projectionName*/, n}, + return {{PartialSchemaRequirements{{{n}, {boost::none /*boundProjectionName*/, IntervalReqExpr::makeSingularDNF(), false /*isPerfOnly*/}}}}}; @@ -615,7 +615,7 @@ public: // If we have a path converter, attempt to convert directly into bounds. if (auto conversion = _pathToInterval(n); conversion) { return {{PartialSchemaRequirements{ - {PartialSchemaKey{"" /*projectionName*/, make<PathIdentity>()}, + {PartialSchemaKey{make<PathIdentity>()}, PartialSchemaRequirement{boost::none /*boundProjectionName*/, std::move(*conversion), false /*isPerfOnly*/}}}}}; @@ -1147,7 +1147,7 @@ CandidateIndexes computeCandidateIndexes(PrefixId& prefixId, if (!req.getIsPerfOnly()) { // Only regular requirements are added to residual predicates. const ProjectionName& tempProjName = getExistingOrTempProjForFieldName( - prefixId, encodeIndexKeyName(indexField), fieldProjMap); + prefixId, FieldNameType{encodeIndexKeyName(indexField)}, fieldProjMap); entry._residualRequirements.emplace_back( PartialSchemaKey{tempProjName, std::move(*fusedPath._suffix)}, req, @@ -1408,7 +1408,7 @@ void lowerPartialSchemaRequirement(const PartialSchemaKey& key, if (const auto& boundProjName = req.getBoundProjectionName()) { node = make<EvaluationNode>(*boundProjName, - make<EvalPath>(key._path, make<Variable>(key._projectionName)), + make<EvalPath>(key._path, make<Variable>(*key._projectionName)), std::move(node)); visitor(node); @@ -1425,9 +1425,9 @@ void lowerPartialSchemaRequirement(const PartialSchemaKey& key, path = key._path; appender.append(path); - node = - make<FilterNode>(make<EvalFilter>(std::move(path), make<Variable>(key._projectionName)), - std::move(node)); + node = make<FilterNode>( + make<EvalFilter>(std::move(path), make<Variable>(*key._projectionName)), + std::move(node)); visitor(node); } } @@ -1522,7 +1522,7 @@ void removeRedundantResidualPredicates(const ProjectionNameOrderPreservingSet& r } } - residualTempProjections.insert(key._projectionName); + residualTempProjections.insert(*key._projectionName); it++; } @@ -1769,7 +1769,7 @@ public: _estimateStack.push_back(childSel); FieldProjectionMap childMap = _fpmStack.back(); - if (childMap._ridProjection.empty()) { + if (!childMap._ridProjection) { childMap._ridProjection = _ridProjName; } if (childCount > 1) { @@ -1799,7 +1799,7 @@ public: } ProjectionNameVector unionProjectionNames; - unionProjectionNames.push_back(innerMap._ridProjection); + unionProjectionNames.push_back(*innerMap._ridProjection); for (const auto& [fieldName, projectionName] : innerMap._fieldProjections) { unionProjectionNames.push_back(projectionName); } @@ -1815,7 +1815,7 @@ public: make<FunctionCall>("$first", makeSeq(make<Variable>(projectionName)))); } - ProjectionName sideSetProjectionName; + boost::optional<ProjectionName> sideSetProjectionName; if constexpr (isIntersect) { const ProjectionName sideIdProjectionName = _prefixId.getNextId("sideId"); unionProjectionNames.push_back(sideIdProjectionName); @@ -1831,13 +1831,13 @@ public: aggExpressions.emplace_back( make<FunctionCall>("$addToSet", makeSeq(make<Variable>(sideIdProjectionName)))); - aggProjectionNames.push_back(sideSetProjectionName); + aggProjectionNames.push_back(*sideSetProjectionName); } ABT result = make<UnionNode>(std::move(unionProjectionNames), std::move(inputs)); _nodeCEMap.emplace(result.cast<Node>(), ce); - result = make<GroupByNode>(ProjectionNameVector{innerMap._ridProjection}, + result = make<GroupByNode>(ProjectionNameVector{*innerMap._ridProjection}, std::move(aggProjectionNames), std::move(aggExpressions), std::move(result)); @@ -1848,7 +1848,7 @@ public: make<EvalFilter>( make<PathCompare>(Operations::Eq, Constant::int64(inputSize)), make<FunctionCall>("getArraySize", - makeSeq(make<Variable>(sideSetProjectionName)))), + makeSeq(make<Variable>(*sideSetProjectionName)))), std::move(result)); _nodeCEMap.emplace(result.cast<Node>(), ce); } diff --git a/src/mongo/db/query/optimizer/utils/utils.h b/src/mongo/db/query/optimizer/utils/utils.h index 48589995a14..1d53c3ef3f1 100644 --- a/src/mongo/db/query/optimizer/utils/utils.h +++ b/src/mongo/db/query/optimizer/utils/utils.h @@ -44,11 +44,13 @@ inline void updateHashUnordered(size_t& result, const size_t hash) { result ^= hash; } -template <class T, class T1 = std::conditional_t<std::is_arithmetic_v<T>, const T, const T&>> +template <class T, + class Hasher = std::hash<T>, + class T1 = std::conditional_t<std::is_arithmetic_v<T>, const T, const T&>> inline size_t computeVectorHash(const std::vector<T>& v) { size_t result = 17; for (T1 e : v) { - updateHash(result, std::hash<T>()(e)); + updateHash(result, Hasher()(e)); } return result; } @@ -89,21 +91,21 @@ inline void maybeComposePaths(ABTVector& paths) { while (paths.size() > 1) { const size_t half = paths.size() / 2; for (size_t i = 0; i < half; i++) { - maybeComposePath<Element>(paths.at(i), std::move(paths.at(paths.size() - i - 1))); + maybeComposePath<Element>(paths.at(i), std::move(paths.back())); + paths.pop_back(); } - paths.resize(paths.size() - half, make<Blackhole>()); } } /** - * Used to vend out fresh ids for projection names. + * Used to vend out fresh projection names. */ class PrefixId { public: - std::string getNextId(const std::string& key); + ProjectionName getNextId(const StringData& prefix); private: - opt::unordered_map<std::string, int> _idCounterPerKey; + opt::unordered_map<std::string, uint64_t> _idCounterPerPrefix; }; ProjectionNameOrderedSet convertToOrderedSet(ProjectionNameSet unordered); @@ -136,7 +138,7 @@ struct CollationSplitResult { * Split a collation requirement between an outer (left) and inner (right) side. The outer side must * be a prefix in the collation spec, and the right side a suffix. */ -CollationSplitResult splitCollationSpec(const ProjectionName& ridProjName, +CollationSplitResult splitCollationSpec(const boost::optional<ProjectionName>& ridProjName, const ProjectionCollationSpec& collationSpec, const ProjectionNameSet& leftProjections, const ProjectionNameSet& rightProjections); diff --git a/src/mongo/db/query/sbe_stage_builder.cpp b/src/mongo/db/query/sbe_stage_builder.cpp index ca8e52b6e1c..6250b02b87c 100644 --- a/src/mongo/db/query/sbe_stage_builder.cpp +++ b/src/mongo/db/query/sbe_stage_builder.cpp @@ -859,7 +859,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder } } - const std::string rootStr = "rowStoreRoot"; + const optimizer::ProjectionName rootStr = "rowStoreRoot"; optimizer::FieldMapBuilder builder(rootStr, true); // When building its output document (in 'recordSlot'), the 'ColumnStoreStage' should not try to |