diff options
author | Ben Shteinfeld <ben.shteinfeld@mongodb.com> | 2022-08-25 21:05:13 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-08-25 22:29:05 +0000 |
commit | 7936a08758813938df3653ca63391b822612f238 (patch) | |
tree | defb93784725319b596febd5480907affdb15b57 | |
parent | 42aef32217428c8d349ceb80ef62d077eafb835e (diff) | |
download | mongo-7936a08758813938df3653ca63391b822612f238.tar.gz |
SERVER-62407 Translate find queries directly to ABT
28 files changed, 897 insertions, 379 deletions
diff --git a/buildscripts/resmokeconfig/suites/cqf_passthrough.yml b/buildscripts/resmokeconfig/suites/cqf_passthrough.yml index 8c6140bc78a..910ffa500c3 100644 --- a/buildscripts/resmokeconfig/suites/cqf_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/cqf_passthrough.yml @@ -50,6 +50,8 @@ selector: - jstests/core/projection_meta_index_key.js - jstests/core/positional_projection.js - jstests/core/positional_projection_multiple_array_fields.js + # TODO SERVER-62034 Prevent distinct() from using CQF. + - jstests/core/profile_distinct.js # Transactions are not supported on MongoDB standalone nodes, so we do not run these tests in the # 'core' suite. Instead we run them against a 1-node replica set in the 'core_txns' suite. - jstests/core/txns/**/*.js diff --git a/jstests/libs/optimizer_utils.js b/jstests/libs/optimizer_utils.js index 9a0cd866e31..a43b635a8de 100644 --- a/jstests/libs/optimizer_utils.js +++ b/jstests/libs/optimizer_utils.js @@ -13,12 +13,8 @@ function checkCascadesOptimizerEnabled(theDB) { * Given the result of an explain command, returns whether the bonsai optimizer was used. */ function usedBonsaiOptimizer(explain) { - if (explain.hasOwnProperty("queryPlanner") && - !explain.queryPlanner.winningPlan.hasOwnProperty("optimizerPlan")) { - // Find command explain which means new optimizer was not used. - // TODO SERVER-62407 this assumption may no longer hold true if the translation to ABT - // happens directly from a find command. - return false; + if (!isAggregationPlan(explain)) { + return explain.queryPlanner.winningPlan.hasOwnProperty("optimizerPlan"); } const plannerOutput = getAggPlanStage(explain, "$cursor"); diff --git a/jstests/noPassthrough/cqf_fallback.js b/jstests/noPassthrough/cqf_fallback.js index 688a75c641b..2157340e97c 100644 --- a/jstests/noPassthrough/cqf_fallback.js +++ b/jstests/noPassthrough/cqf_fallback.js @@ -116,6 +116,9 @@ assertNotSupportedByBonsai({ }, true); +// Sort on a find() is not supported. +assertNotSupportedByBonsai({find: coll.getName(), filter: {}, sort: {a: 1}}, true); + // Numeric path components are not supported, either in a match expression or projection. assertNotSupportedByBonsai({find: coll.getName(), filter: {'a.0': 5}}); assertNotSupportedByBonsai({find: coll.getName(), filter: {'a.0.b': 5}}); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index a9404ebedfa..e4d96586964 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -1391,6 +1391,8 @@ env.Library( 'query/all_indices_required_checker.cpp', 'query/bind_input_params.cpp', 'query/classic_stage_builder.cpp', + 'query/cqf_command_utils.cpp', + 'query/cqf_get_executor.cpp', 'query/explain.cpp', 'query/find.cpp', 'query/get_executor.cpp', @@ -1429,6 +1431,7 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/auth/auth_checks', '$BUILD_DIR/mongo/db/index/index_access_methods', + '$BUILD_DIR/mongo/db/query/ce/query_ce', '$BUILD_DIR/mongo/scripting/scripting', '$BUILD_DIR/mongo/util/background_job', '$BUILD_DIR/mongo/util/elapsed_tracker', diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 56fc590f96f..e83f7356c54 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -338,8 +338,6 @@ env.Library( source=[ "analyze_cmd.cpp", "count_cmd.cpp", - "cqf/cqf_aggregate.cpp", - "cqf/cqf_command_utils.cpp", "create_command.cpp", "create_indexes.cpp", "current_op.cpp", diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 9ebf3c97908..86bff60820b 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -36,7 +36,6 @@ #include "mongo/db/client.h" #include "mongo/db/clientcursor.h" #include "mongo/db/commands.h" -#include "mongo/db/commands/cqf/cqf_command_utils.h" #include "mongo/db/commands/run_aggregate.h" #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/cursor_manager.h" @@ -48,6 +47,8 @@ #include "mongo/db/pipeline/aggregation_request_helper.h" #include "mongo/db/pipeline/variables.h" #include "mongo/db/query/collation/collator_factory_interface.h" +#include "mongo/db/query/cqf_command_utils.h" +#include "mongo/db/query/cqf_get_executor.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/explain.h" #include "mongo/db/query/find.h" @@ -309,9 +310,9 @@ public: extensionsCallback, MatchExpressionParser::kAllowAllSpecialFeatures)); - // If we are running a query against a view, or if we are trying to test the new - // optimizer, redirect this query through the aggregation system. - if (ctx->getView() || isEligibleForBonsai(*cq, opCtx, ctx->getCollection())) { + // If we are running a query against a view redirect this query through the aggregation + // system. + if (ctx->getView()) { // Relinquish locks. The aggregation command will re-acquire them. ctx.reset(); @@ -514,9 +515,9 @@ public: extensionsCallback, MatchExpressionParser::kAllowAllSpecialFeatures)); - // If we are running a query against a view, or if we are trying to test the new - // optimizer, redirect this query through the aggregation system. - if (ctx->getView() || isEligibleForBonsai(*cq, opCtx, ctx->getCollection())) { + // If we are running a query against a view redirect this query through the aggregation + // system. + if (ctx->getView()) { // Relinquish locks. The aggregation command will re-acquire them. ctx.reset(); diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index acc2597af08..28909dd442b 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -41,8 +41,6 @@ #include "mongo/db/catalog/database_holder.h" #include "mongo/db/change_stream_change_collection_manager.h" #include "mongo/db/change_stream_pre_images_collection_manager.h" -#include "mongo/db/commands/cqf/cqf_aggregate.h" -#include "mongo/db/commands/cqf/cqf_command_utils.h" #include "mongo/db/curop.h" #include "mongo/db/cursor_manager.h" #include "mongo/db/db_raii.h" @@ -66,6 +64,8 @@ #include "mongo/db/pipeline/search_helper.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/cqf_command_utils.h" +#include "mongo/db/query/cqf_get_executor.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/find_common.h" #include "mongo/db/query/get_executor.h" @@ -976,8 +976,12 @@ Status runAggregate(OperationContext* opCtx, !request.getExchange().has_value()); auto timeBegin = Date_t::now(); - execs.emplace_back(getSBEExecutorViaCascadesOptimizer( - opCtx, expCtx, nss, collections.getMainCollection(), request.getHint(), *pipeline)); + execs.emplace_back(getSBEExecutorViaCascadesOptimizer(opCtx, + expCtx, + nss, + collections.getMainCollection(), + request.getHint(), + std::move(pipeline))); auto elapsed = (Date_t::now().toMillisSinceEpoch() - timeBegin.toMillisSinceEpoch()) / 1000.0; OPTIMIZER_DEBUG_LOG( diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index aaf0551b82f..377d8f60cc3 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -120,6 +120,9 @@ RecordId Helpers::findOne(OperationContext* opCtx, massertStatusOK(statusWithCQ.getStatus()); unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); + // TODO SERVER-69102: Enable CQF once it supports RecordId outputs. + cq->setUseCqfIfEligible(false); + auto exec = uassertStatusOK(getExecutor(opCtx, &collection, std::move(cq), diff --git a/src/mongo/db/exec/sbe/abt/sbe_abt_test_util.cpp b/src/mongo/db/exec/sbe/abt/sbe_abt_test_util.cpp index 4b6ce1a09a7..cfb6372aae3 100644 --- a/src/mongo/db/exec/sbe/abt/sbe_abt_test_util.cpp +++ b/src/mongo/db/exec/sbe/abt/sbe_abt_test_util.cpp @@ -29,12 +29,12 @@ #include "mongo/db/exec/sbe/abt/sbe_abt_test_util.h" -#include "mongo/db/commands/cqf/cqf_command_utils.h" #include "mongo/db/exec/sbe/abt/abt_lower.h" #include "mongo/db/pipeline/abt/document_source_visitor.h" #include "mongo/db/pipeline/aggregate_command_gen.h" #include "mongo/db/pipeline/document_source_queue.h" #include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/query/cqf_command_utils.h" #include "mongo/db/query/optimizer/explain.h" #include "mongo/db/query/optimizer/opt_phase_manager.h" #include "mongo/db/query/plan_executor.h" diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index a01d33f0702..cf92ade7e0a 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -223,11 +223,14 @@ pipelineEnv.InjectThirdParty(libraries=['snappy']) pipelineEnv.Library( target='pipeline', source=[ + 'abt/canonical_query_translation.cpp', 'abt/document_source_visitor.cpp', 'abt/agg_expression_visitor.cpp', + 'abt/collation_translation.cpp', 'abt/expr_algebrizer_context.cpp', 'abt/field_map_builder.cpp', 'abt/match_expression_visitor.cpp', + 'abt/transformer_visitor.cpp', 'abt/utils.cpp', 'document_source.cpp', 'document_source_add_fields.cpp', diff --git a/src/mongo/db/pipeline/abt/algebrizer_context.h b/src/mongo/db/pipeline/abt/algebrizer_context.h new file mode 100644 index 00000000000..c85c7dde0c7 --- /dev/null +++ b/src/mongo/db/pipeline/abt/algebrizer_context.h @@ -0,0 +1,92 @@ +/** + * 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/db/query/optimizer/defs.h" +#include "mongo/db/query/optimizer/node.h" +#include "mongo/db/query/optimizer/syntax/syntax.h" +#include "mongo/db/query/optimizer/utils/utils.h" + +namespace mongo::optimizer { + +/** + * Used to track information including the current root node of the ABT and the current projection + * representing output documents. + */ +class AlgebrizerContext { +public: + struct NodeWithRootProjection { + NodeWithRootProjection(ProjectionName rootProjection, ABT node) + : _rootProjection(std::move(rootProjection)), _node(std::move(node)) {} + + ProjectionName _rootProjection; + ABT _node; + }; + + AlgebrizerContext(PrefixId& prefixId, NodeWithRootProjection node) + : _node(std::move(node)), _scanProjName(_node._rootProjection), _prefixId(prefixId) { + assertNodeSort(_node._node); + } + + template <typename T, typename... Args> + inline auto setNode(ProjectionName rootProjection, Args&&... args) { + setNode(std::move(rootProjection), std::move(ABT::make<T>(std::forward<Args>(args)...))); + } + + void setNode(ProjectionName rootProjection, ABT node) { + assertNodeSort(node); + _node._node = std::move(node); + _node._rootProjection = std::move(rootProjection); + } + + NodeWithRootProjection& getNode() { + return _node; + } + + std::string getNextId(const std::string& key) { + return _prefixId.getNextId(key); + } + + PrefixId& getPrefixId() { + return _prefixId; + } + + const ProjectionName& getScanProjName() const { + return _scanProjName; + } + +private: + NodeWithRootProjection _node; + ProjectionName _scanProjName; + + PrefixId& _prefixId; +}; + +} // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/canonical_query_translation.cpp b/src/mongo/db/pipeline/abt/canonical_query_translation.cpp new file mode 100644 index 00000000000..9501b855053 --- /dev/null +++ b/src/mongo/db/pipeline/abt/canonical_query_translation.cpp @@ -0,0 +1,68 @@ +/** + * 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. + */ + +#include "mongo/db/pipeline/abt/canonical_query_translation.h" + +#include "mongo/db/pipeline/abt/algebrizer_context.h" +#include "mongo/db/pipeline/abt/collation_translation.h" +#include "mongo/db/pipeline/abt/match_expression_visitor.h" +#include "mongo/db/pipeline/abt/transformer_visitor.h" + +namespace mongo::optimizer { + +ABT translateCanonicalQueryToABT(const Metadata& metadata, + const CanonicalQuery& canonicalQuery, + ProjectionName scanProjName, + ABT initialNode, + PrefixId& prefixId) { + auto abt = generateMatchExpression( + canonicalQuery.root(), false /* allowAggExpression */, scanProjName, "match"); + + abt = make<FilterNode>(make<EvalFilter>(std::move(abt), make<Variable>(scanProjName)), + std::move(initialNode)); + + AlgebrizerContext ctx{prefixId, {scanProjName, std::move(abt)}}; + + if (auto sortPattern = canonicalQuery.getSortPattern()) { + generateCollationNode(ctx, *sortPattern); + } + + if (auto proj = canonicalQuery.getProj()) { + translateProjection(ctx, scanProjName, canonicalQuery.getExpCtx(), proj); + } + + // TODO SERVER-68692: Support limit. + // TODO SERVER-68693: Support skip. + + return make<RootNode>(properties::ProjectionRequirement{ProjectionNameVector{ + std::move(ctx.getNode()._rootProjection)}}, + std::move(ctx.getNode()._node)); +} + +} // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/canonical_query_translation.h b/src/mongo/db/pipeline/abt/canonical_query_translation.h new file mode 100644 index 00000000000..d1a0eb33006 --- /dev/null +++ b/src/mongo/db/pipeline/abt/canonical_query_translation.h @@ -0,0 +1,46 @@ +/** + * 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/db/query/canonical_query.h" +#include "mongo/db/query/optimizer/defs.h" +#include "mongo/db/query/optimizer/metadata.h" +#include "mongo/db/query/optimizer/syntax/syntax.h" +#include "mongo/db/query/optimizer/utils/utils.h" + +namespace mongo::optimizer { + +ABT translateCanonicalQueryToABT(const Metadata& metadata, + const CanonicalQuery& canonicalQuery, + ProjectionName scanProjName, + ABT initialNode, + PrefixId& prefixId); + +} // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/collation_translation.cpp b/src/mongo/db/pipeline/abt/collation_translation.cpp new file mode 100644 index 00000000000..56bc3345fe7 --- /dev/null +++ b/src/mongo/db/pipeline/abt/collation_translation.cpp @@ -0,0 +1,65 @@ +/** + * 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. + */ + +#include "mongo/db/pipeline/abt/collation_translation.h" + +namespace mongo::optimizer { + +void generateCollationNode(AlgebrizerContext& ctx, const SortPattern& sortPattern) { + ProjectionCollationSpec collationSpec; + auto rootProjection = ctx.getNode()._rootProjection; + // Create Evaluation node for each sort field. + for (const auto& part : sortPattern) { + if (!part.fieldPath.has_value()) { + continue; + } + const auto& sortProjName = ctx.getNextId("sort"); + collationSpec.emplace_back( + sortProjName, part.isAscending ? CollationOp::Ascending : CollationOp::Descending); + 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)); + } + + ctx.setNode<EvaluationNode>( + rootProjection, + std::move(sortProjName), + make<EvalPath>(std::move(sortPath), make<Variable>(rootProjection)), + std::move(ctx.getNode()._node)); + } + if (collationSpec.empty()) { + return; + } + ctx.setNode<CollationNode>(std::move(rootProjection), + properties::CollationRequirement(std::move(collationSpec)), + std::move(ctx.getNode()._node)); +} + +} // namespace mongo::optimizer diff --git a/src/mongo/db/commands/cqf/cqf_aggregate.h b/src/mongo/db/pipeline/abt/collation_translation.h index ec7ee64b257..cfaf7e750d3 100644 --- a/src/mongo/db/commands/cqf/cqf_aggregate.h +++ b/src/mongo/db/pipeline/abt/collation_translation.h @@ -29,17 +29,13 @@ #pragma once -#include "mongo/db/catalog/collection.h" -#include "mongo/db/query/plan_executor.h" +#include "mongo/db/pipeline/abt/algebrizer_context.h" +#include "mongo/db/query/optimizer/syntax/syntax.h" +#include "mongo/db/query/optimizer/utils/utils.h" +#include "mongo/db/query/sort_pattern.h" -namespace mongo { +namespace mongo::optimizer { -std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( - OperationContext* opCtx, - boost::intrusive_ptr<ExpressionContext> expCtx, - const NamespaceString& nss, - const CollectionPtr& collection, - const boost::optional<BSONObj>& indexHint, - const Pipeline& pipeline); +void generateCollationNode(AlgebrizerContext& ctx, const SortPattern& sortPattern); -} // namespace mongo +} // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/document_source_visitor.cpp b/src/mongo/db/pipeline/abt/document_source_visitor.cpp index af9b741b345..f563b93f96f 100644 --- a/src/mongo/db/pipeline/abt/document_source_visitor.cpp +++ b/src/mongo/db/pipeline/abt/document_source_visitor.cpp @@ -29,12 +29,11 @@ #include "mongo/db/pipeline/abt/document_source_visitor.h" -#include "mongo/db/exec/add_fields_projection_executor.h" -#include "mongo/db/exec/exclusion_projection_executor.h" -#include "mongo/db/exec/inclusion_projection_executor.h" #include "mongo/db/pipeline/abt/agg_expression_visitor.h" -#include "mongo/db/pipeline/abt/field_map_builder.h" +#include "mongo/db/pipeline/abt/algebrizer_context.h" +#include "mongo/db/pipeline/abt/collation_translation.h" #include "mongo/db/pipeline/abt/match_expression_visitor.h" +#include "mongo/db/pipeline/abt/transformer_visitor.h" #include "mongo/db/pipeline/abt/utils.h" #include "mongo/db/pipeline/document_source_bucket_auto.h" #include "mongo/db/pipeline/document_source_coll_stats.h" @@ -80,243 +79,9 @@ namespace mongo::optimizer { -/** - * Used to track information including the current root node of the ABT and the current projection - * representing output documents. - */ -class DSAlgebrizerContext { -public: - struct NodeWithRootProjection { - NodeWithRootProjection(ProjectionName rootProjection, ABT node) - : _rootProjection(std::move(rootProjection)), _node(std::move(node)) {} - - ProjectionName _rootProjection; - ABT _node; - }; - - DSAlgebrizerContext(PrefixId& prefixId, NodeWithRootProjection node) - : _node(std::move(node)), _scanProjName(_node._rootProjection), _prefixId(prefixId) { - assertNodeSort(_node._node); - } - - template <typename T, typename... Args> - inline auto setNode(ProjectionName rootProjection, Args&&... args) { - setNode(std::move(rootProjection), std::move(ABT::make<T>(std::forward<Args>(args)...))); - } - - void setNode(ProjectionName rootProjection, ABT node) { - assertNodeSort(node); - _node._node = std::move(node); - _node._rootProjection = std::move(rootProjection); - } - - NodeWithRootProjection& getNode() { - return _node; - } - - std::string getNextId(const std::string& key) { - return _prefixId.getNextId(key); - } - - PrefixId& getPrefixId() { - return _prefixId; - } - - const ProjectionName& getScanProjName() const { - return _scanProjName; - } - -private: - NodeWithRootProjection _node; - ProjectionName _scanProjName; - - // We don't own this. - PrefixId& _prefixId; -}; - -class ABTTransformerVisitor : public TransformerInterfaceConstVisitor { -public: - ABTTransformerVisitor(DSAlgebrizerContext& ctx, FieldMapBuilder& builder) - : _ctx(ctx), _builder(builder) {} - - void visit(const projection_executor::AddFieldsProjectionExecutor* transformer) override { - visitInclusionNode(transformer->getRoot(), true /*isAddingFields*/); - } - - void visit(const projection_executor::ExclusionProjectionExecutor* transformer) override { - visitExclusionNode(*transformer->getRoot()); - } - - void visit(const projection_executor::InclusionProjectionExecutor* transformer) override { - visitInclusionNode(*transformer->getRoot(), false /*isAddingFields*/); - } - - void visit(const GroupFromFirstDocumentTransformation* transformer) override { - unsupportedTransformer(transformer); - } - - void visit(const ReplaceRootTransformation* transformer) override { - auto entry = _ctx.getNode(); - const std::string& projName = _ctx.getNextId("newRoot"); - ABT expr = generateAggExpression( - transformer->getExpression().get(), entry._rootProjection, projName); - - _ctx.setNode<EvaluationNode>(projName, projName, std::move(expr), std::move(entry._node)); - } - - // Creates a single EvaluationNode representing simple projections (e.g. inclusion projections) - // and computed projections, if present, and updates the context with the new node. - void generateCombinedProjection() const { - auto result = _builder.generateABT(); - if (!result) { - return; - } - - auto entry = _ctx.getNode(); - const ProjectionName projName = _ctx.getNextId("combinedProjection"); - _ctx.setNode<EvaluationNode>( - projName, projName, std::move(*result), std::move(entry._node)); - } - -private: - void unsupportedTransformer(const TransformerInterface* transformer) const { - uasserted(ErrorCodes::InternalErrorNotSupported, - str::stream() << "Transformer is not supported (code: " - << static_cast<int>(transformer->getType()) << ")"); - } - - void assertSupportedPath(const std::string& path) { - uassert(ErrorCodes::InternalErrorNotSupported, - "Projection contains unsupported numeric path component", - !FieldRef(path).hasNumericPathComponents()); - } - - /** - * Handles simple inclusion projections. - */ - void processProjectedPaths(const projection_executor::InclusionNode& node) { - // For each preserved path, mark that each path element along the field path should be - // included. - OrderedPathSet preservedPaths; - node.reportProjectedPaths(&preservedPaths); - - for (const std::string& preservedPathStr : preservedPaths) { - assertSupportedPath(preservedPathStr); - - _builder.integrateFieldPath(FieldPath(preservedPathStr), - [](const bool isLastElement, FieldMapEntry& entry) { - entry._hasLeadingObj = true; - entry._hasKeep = true; - }); - } - } - - /** - * Handles renamed fields and computed projections. - */ - void processComputedPaths(const projection_executor::InclusionNode& node, - const std::string& rootProjection, - const bool isAddingFields) { - OrderedPathSet computedPaths; - StringMap<std::string> renamedPaths; - node.reportComputedPaths(&computedPaths, &renamedPaths); - - // Handle path renames: essentially single element FieldPath expression. - for (const auto& renamedPathEntry : renamedPaths) { - ABT path = translateFieldPath( - FieldPath(renamedPathEntry.second), - make<PathIdentity>(), - [](const std::string& fieldName, const bool isLastElement, ABT input) { - return make<PathGet>( - fieldName, - isLastElement - ? std::move(input) - : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited)); - }); - - auto entry = _ctx.getNode(); - const std::string& renamedProjName = _ctx.getNextId("projRenamedPath"); - _ctx.setNode<EvaluationNode>( - entry._rootProjection, - renamedProjName, - make<EvalPath>(std::move(path), make<Variable>(entry._rootProjection)), - std::move(entry._node)); - - _builder.integrateFieldPath(FieldPath(renamedPathEntry.first), - [&renamedProjName, &isAddingFields]( - const bool isLastElement, FieldMapEntry& entry) { - if (!isAddingFields) { - entry._hasKeep = true; - } - if (isLastElement) { - entry._constVarName = renamedProjName; - entry._hasTrailingDefault = true; - } - }); - } - - // Handle general expression projection. - for (const std::string& computedPathStr : computedPaths) { - assertSupportedPath(computedPathStr); - - const FieldPath computedPath(computedPathStr); - - auto entry = _ctx.getNode(); - const std::string& getProjName = _ctx.getNextId("projGetPath"); - ABT getExpr = generateAggExpression( - node.getExpressionForPath(computedPath).get(), rootProjection, getProjName); - - _ctx.setNode<EvaluationNode>(std::move(entry._rootProjection), - getProjName, - std::move(getExpr), - std::move(entry._node)); - - _builder.integrateFieldPath( - computedPath, - [&getProjName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) { - if (!isAddingFields) { - entry._hasKeep = true; - } - if (isLastElement) { - entry._constVarName = getProjName; - entry._hasTrailingDefault = true; - } - }); - } - } - - void visitInclusionNode(const projection_executor::InclusionNode& node, - const bool isAddingFields) { - auto entry = _ctx.getNode(); - const std::string rootProjection = entry._rootProjection; - - processProjectedPaths(node); - processComputedPaths(node, rootProjection, isAddingFields); - } - - void visitExclusionNode(const projection_executor::ExclusionNode& node) { - // Handle simple exclusion projections: for each excluded path, mark that the last field - // path element should be dropped. - OrderedPathSet excludedPaths; - node.reportProjectedPaths(&excludedPaths); - for (const std::string& excludedPathStr : excludedPaths) { - assertSupportedPath(excludedPathStr); - _builder.integrateFieldPath(FieldPath(excludedPathStr), - [](const bool isLastElement, FieldMapEntry& entry) { - if (isLastElement) { - entry._hasDrop = true; - } - }); - } - } - - DSAlgebrizerContext& _ctx; - FieldMapBuilder& _builder; -}; - class ABTDocumentSourceVisitor : public DocumentSourceConstVisitor { public: - ABTDocumentSourceVisitor(DSAlgebrizerContext& ctx, const Metadata& metadata) + ABTDocumentSourceVisitor(AlgebrizerContext& ctx, const Metadata& metadata) : _ctx(ctx), _metadata(metadata) {} void visit(const DocumentSourceBucketAuto* source) override { @@ -717,39 +482,7 @@ public: } void visit(const DocumentSourceSort* source) override { - ProjectionCollationSpec collationSpec; - const SortPattern& pattern = source->getSortKeyPattern(); - for (size_t i = 0; i < pattern.size(); i++) { - const SortPattern::SortPatternPart& part = pattern[i]; - if (!part.fieldPath.has_value()) { - // TODO: consider metadata expression. - continue; - } - - const std::string& sortProjName = _ctx.getNextId("sort"); - collationSpec.emplace_back( - sortProjName, part.isAscending ? CollationOp::Ascending : CollationOp::Descending); - - 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)); - } - - auto entry = _ctx.getNode(); - _ctx.setNode<EvaluationNode>( - entry._rootProjection, - sortProjName, - make<EvalPath>(std::move(sortPath), make<Variable>(entry._rootProjection)), - std::move(entry._node)); - } - - if (!collationSpec.empty()) { - auto entry = _ctx.getNode(); - _ctx.setNode<CollationNode>(std::move(entry._rootProjection), - properties::CollationRequirement(std::move(collationSpec)), - std::move(entry._node)); - } + generateCollationNode(_ctx, source->getSortKeyPattern()); if (source->getLimit().has_value()) { // We need to limit the result of the collation. @@ -906,7 +639,7 @@ private: std::move(entry._node)); } - DSAlgebrizerContext& _ctx; + AlgebrizerContext& _ctx; const Metadata& _metadata; }; @@ -915,7 +648,7 @@ ABT translatePipelineToABT(const Metadata& metadata, ProjectionName scanProjName, ABT initialNode, PrefixId& prefixId) { - DSAlgebrizerContext ctx(prefixId, {scanProjName, std::move(initialNode)}); + AlgebrizerContext ctx(prefixId, {scanProjName, std::move(initialNode)}); ABTDocumentSourceVisitor visitor(ctx, metadata); DocumentSourceWalker walker(nullptr /*preVisitor*/, &visitor); diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.cpp b/src/mongo/db/pipeline/abt/transformer_visitor.cpp new file mode 100644 index 00000000000..ccfbeb56e31 --- /dev/null +++ b/src/mongo/db/pipeline/abt/transformer_visitor.cpp @@ -0,0 +1,230 @@ +/** + * 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. + */ + +#include "mongo/db/pipeline/abt/transformer_visitor.h" + +#include "mongo/db/pipeline/abt/agg_expression_visitor.h" +#include "mongo/db/pipeline/abt/utils.h" +#include "mongo/db/pipeline/document_source_group.h" +#include "mongo/db/pipeline/document_source_replace_root.h" +#include "mongo/db/pipeline/visitors/transformer_interface_walker.h" + +namespace mongo::optimizer { + +void ABTTransformerVisitor::visit( + const projection_executor::AddFieldsProjectionExecutor* transformer) { + visitInclusionNode(transformer->getRoot(), true /*isAddingFields*/); +} + +void ABTTransformerVisitor::visit( + const projection_executor::ExclusionProjectionExecutor* transformer) { + visitExclusionNode(*transformer->getRoot()); +} + +void ABTTransformerVisitor::visit( + const projection_executor::InclusionProjectionExecutor* transformer) { + visitInclusionNode(*transformer->getRoot(), false /*isAddingFields*/); +} + +void ABTTransformerVisitor::visit(const GroupFromFirstDocumentTransformation* transformer) { + unsupportedTransformer(transformer); +} + +void ABTTransformerVisitor::visit(const ReplaceRootTransformation* transformer) { + auto entry = _ctx.getNode(); + const std::string& projName = _ctx.getNextId("newRoot"); + ABT expr = + generateAggExpression(transformer->getExpression().get(), entry._rootProjection, projName); + + _ctx.setNode<EvaluationNode>(projName, projName, std::move(expr), std::move(entry._node)); +} + +void ABTTransformerVisitor::generateCombinedProjection() const { + auto result = _builder.generateABT(); + if (!result) { + return; + } + + auto entry = _ctx.getNode(); + const ProjectionName projName = _ctx.getNextId("combinedProjection"); + _ctx.setNode<EvaluationNode>(projName, projName, std::move(*result), std::move(entry._node)); +} + +void ABTTransformerVisitor::unsupportedTransformer(const TransformerInterface* transformer) const { + uasserted(ErrorCodes::InternalErrorNotSupported, + str::stream() << "Transformer is not supported (code: " + << static_cast<int>(transformer->getType()) << ")"); +} + +void ABTTransformerVisitor::assertSupportedPath(const std::string& path) { + uassert(ErrorCodes::InternalErrorNotSupported, + "Projection contains unsupported numeric path component", + !FieldRef(path).hasNumericPathComponents()); +} + +/** + * Handles simple inclusion projections. + */ +void ABTTransformerVisitor::processProjectedPaths(const projection_executor::InclusionNode& node) { + // For each preserved path, mark that each path element along the field path should be + // included. + OrderedPathSet preservedPaths; + node.reportProjectedPaths(&preservedPaths); + + for (const std::string& preservedPathStr : preservedPaths) { + assertSupportedPath(preservedPathStr); + + _builder.integrateFieldPath(FieldPath(preservedPathStr), + [](const bool isLastElement, FieldMapEntry& entry) { + entry._hasLeadingObj = true; + entry._hasKeep = true; + }); + } +} + +/** + * Handles renamed fields and computed projections. + */ +void ABTTransformerVisitor::processComputedPaths(const projection_executor::InclusionNode& node, + const std::string& rootProjection, + const bool isAddingFields) { + OrderedPathSet computedPaths; + StringMap<std::string> renamedPaths; + node.reportComputedPaths(&computedPaths, &renamedPaths); + + // Handle path renames: essentially single element FieldPath expression. + for (const auto& renamedPathEntry : renamedPaths) { + ABT path = translateFieldPath( + FieldPath(renamedPathEntry.second), + make<PathIdentity>(), + [](const std::string& fieldName, const bool isLastElement, ABT input) { + return make<PathGet>( + fieldName, + isLastElement ? std::move(input) + : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited)); + }); + + auto entry = _ctx.getNode(); + const std::string& renamedProjName = _ctx.getNextId("projRenamedPath"); + _ctx.setNode<EvaluationNode>( + entry._rootProjection, + renamedProjName, + make<EvalPath>(std::move(path), make<Variable>(entry._rootProjection)), + std::move(entry._node)); + + _builder.integrateFieldPath( + FieldPath(renamedPathEntry.first), + [&renamedProjName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) { + if (!isAddingFields) { + entry._hasKeep = true; + } + if (isLastElement) { + entry._constVarName = renamedProjName; + entry._hasTrailingDefault = true; + } + }); + } + + // Handle general expression projection. + for (const std::string& computedPathStr : computedPaths) { + assertSupportedPath(computedPathStr); + + const FieldPath computedPath(computedPathStr); + + auto entry = _ctx.getNode(); + const std::string& getProjName = _ctx.getNextId("projGetPath"); + ABT getExpr = generateAggExpression( + node.getExpressionForPath(computedPath).get(), rootProjection, getProjName); + + _ctx.setNode<EvaluationNode>(std::move(entry._rootProjection), + getProjName, + std::move(getExpr), + std::move(entry._node)); + + _builder.integrateFieldPath( + computedPath, + [&getProjName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) { + if (!isAddingFields) { + entry._hasKeep = true; + } + if (isLastElement) { + entry._constVarName = getProjName; + entry._hasTrailingDefault = true; + } + }); + } +} + +void ABTTransformerVisitor::visitInclusionNode(const projection_executor::InclusionNode& node, + const bool isAddingFields) { + auto entry = _ctx.getNode(); + const std::string rootProjection = entry._rootProjection; + + processProjectedPaths(node); + processComputedPaths(node, rootProjection, isAddingFields); +} + +void ABTTransformerVisitor::visitExclusionNode(const projection_executor::ExclusionNode& node) { + // Handle simple exclusion projections: for each excluded path, mark that the last field + // path element should be dropped. + OrderedPathSet excludedPaths; + node.reportProjectedPaths(&excludedPaths); + for (const std::string& excludedPathStr : excludedPaths) { + assertSupportedPath(excludedPathStr); + _builder.integrateFieldPath(FieldPath(excludedPathStr), + [](const bool isLastElement, FieldMapEntry& entry) { + if (isLastElement) { + entry._hasDrop = true; + } + }); + } +} + +/** + * TODO SERVER-68690: Refactor this function to be shared with the DocumentSource implementation. + */ +void translateProjection(AlgebrizerContext& ctx, + ProjectionName scanProjName, + boost::intrusive_ptr<ExpressionContext> expCtx, + const projection_ast::Projection* proj) { + + const auto projExecutor = projection_executor::buildProjectionExecutor( + expCtx, + proj, + ProjectionPolicies::findProjectionPolicies(), + projection_executor::BuilderParamsBitSet{projection_executor::kDefaultBuilderParams}); + + FieldMapBuilder builder(scanProjName, true); + ABTTransformerVisitor visitor(ctx, builder); + TransformerInterfaceWalker walker(&visitor); + walker.walk(projExecutor.get()); + visitor.generateCombinedProjection(); +} + +} // namespace mongo::optimizer diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.h b/src/mongo/db/pipeline/abt/transformer_visitor.h new file mode 100644 index 00000000000..c29de4f6169 --- /dev/null +++ b/src/mongo/db/pipeline/abt/transformer_visitor.h @@ -0,0 +1,93 @@ +/** + * 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/db/exec/add_fields_projection_executor.h" +#include "mongo/db/exec/exclusion_projection_executor.h" +#include "mongo/db/exec/inclusion_projection_executor.h" +#include "mongo/db/exec/projection_executor_builder.h" +#include "mongo/db/pipeline/abt/algebrizer_context.h" +#include "mongo/db/pipeline/abt/field_map_builder.h" +#include "mongo/db/pipeline/visitors/transformer_interface_visitor.h" + +namespace mongo::optimizer { + +class ABTTransformerVisitor : public TransformerInterfaceConstVisitor { +public: + ABTTransformerVisitor(AlgebrizerContext& ctx, FieldMapBuilder& builder) + : _ctx(ctx), _builder(builder) {} + + void visit(const projection_executor::AddFieldsProjectionExecutor* transformer) override; + + void visit(const projection_executor::ExclusionProjectionExecutor* transformer) override; + + void visit(const projection_executor::InclusionProjectionExecutor* transformer) override; + + void visit(const GroupFromFirstDocumentTransformation* transformer) override; + + void visit(const ReplaceRootTransformation* transformer) override; + + /** + * Creates a single EvaluationNode representing simple projections (e.g. inclusion projections) + * and computed projections, if present, and updates the context with the new node. + */ + void generateCombinedProjection() const; + +private: + void unsupportedTransformer(const TransformerInterface* transformer) const; + + void assertSupportedPath(const std::string& path); + + /** + * Handles simple inclusion projections. + */ + void processProjectedPaths(const projection_executor::InclusionNode& node); + + /** + * Handles renamed fields and computed projections. + */ + void processComputedPaths(const projection_executor::InclusionNode& node, + const std::string& rootProjection, + bool isAddingFields); + + void visitInclusionNode(const projection_executor::InclusionNode& node, bool isAddingFields); + + void visitExclusionNode(const projection_executor::ExclusionNode& node); + + AlgebrizerContext& _ctx; + FieldMapBuilder& _builder; +}; + +void translateProjection(AlgebrizerContext& ctx, + ProjectionName scanProjName, + boost::intrusive_ptr<ExpressionContext> expCtx, + const projection_ast::Projection* proj); + +} // namespace mongo::optimizer diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h index 1c0213d0132..0d050d82c63 100644 --- a/src/mongo/db/query/canonical_query.h +++ b/src/mongo/db/query/canonical_query.h @@ -233,6 +233,14 @@ public: return _sbeCompatible; } + void setUseCqfIfEligible(bool useCqfIfEligible) { + _useCqfIfEligible = useCqfIfEligible; + } + + bool useCqfIfEligible() const { + return _useCqfIfEligible; + } + bool isParameterized() const { return !_inputParamIdToExpressionMap.empty(); } @@ -310,6 +318,15 @@ private: // True if this query can be executed by the SBE. bool _sbeCompatible = false; + // If true, indicates that we should use CQF if this query is eligible (see the + // isEligibleForBonsai() function for eligiblitly requirements). + // If false, indicates that we shouldn't use CQF even if this query is eligible. + // Warning: This field is used soley as a workaround for SERVER-69102. It is intended to be + // temporary and will be removed once SERVER-69102 is fixed. Do not introduce new uses or + // dependencies on this field. + // TODO SERVER-69102: Delete this field. + bool _useCqfIfEligible = true; + // A map from assigned InputParamId's to parameterised MatchExpression's. std::vector<const MatchExpression*> _inputParamIdToExpressionMap; }; diff --git a/src/mongo/db/query/ce/ce_sampling.cpp b/src/mongo/db/query/ce/ce_sampling.cpp index 3ee77deaf93..37c9cd60e91 100644 --- a/src/mongo/db/query/ce/ce_sampling.cpp +++ b/src/mongo/db/query/ce/ce_sampling.cpp @@ -29,8 +29,8 @@ #include "mongo/db/query/ce/ce_sampling.h" -#include "mongo/db/commands/cqf/cqf_command_utils.h" #include "mongo/db/exec/sbe/abt/abt_lower.h" +#include "mongo/db/query/cqf_command_utils.h" #include "mongo/db/query/optimizer/cascades/ce_heuristic.h" #include "mongo/db/query/optimizer/explain.h" #include "mongo/db/query/optimizer/utils/abt_hash.h" diff --git a/src/mongo/db/commands/cqf/cqf_command_utils.cpp b/src/mongo/db/query/cqf_command_utils.cpp index 3a64454a60b..ac4a74e0efb 100644 --- a/src/mongo/db/commands/cqf/cqf_command_utils.cpp +++ b/src/mongo/db/query/cqf_command_utils.cpp @@ -27,7 +27,7 @@ * it in the license file. */ -#include "mongo/db/commands/cqf/cqf_command_utils.h" +#include "mongo/db/query/cqf_command_utils.h" #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/exec/add_fields_projection_executor.h" @@ -746,7 +746,7 @@ bool isEligibleForBonsai(const CanonicalQuery& cq, walker.walk(projExecutor.get()); } - return eligible; + return eligible && cq.useCqfIfEligible(); } } // namespace mongo diff --git a/src/mongo/db/commands/cqf/cqf_command_utils.h b/src/mongo/db/query/cqf_command_utils.h index 4ceb333d364..4ceb333d364 100644 --- a/src/mongo/db/commands/cqf/cqf_command_utils.h +++ b/src/mongo/db/query/cqf_command_utils.h diff --git a/src/mongo/db/commands/cqf/cqf_aggregate.cpp b/src/mongo/db/query/cqf_get_executor.cpp index b7d1717b8f7..19f6961cf1a 100644 --- a/src/mongo/db/commands/cqf/cqf_aggregate.cpp +++ b/src/mongo/db/query/cqf_get_executor.cpp @@ -27,17 +27,18 @@ * it in the license file. */ -#include "mongo/db/commands/cqf/cqf_aggregate.h" +#include "mongo/db/query/cqf_get_executor.h" -#include "mongo/db/commands/cqf/cqf_command_utils.h" #include "mongo/db/curop.h" #include "mongo/db/exec/sbe/abt/abt_lower.h" +#include "mongo/db/pipeline/abt/canonical_query_translation.h" #include "mongo/db/pipeline/abt/document_source_visitor.h" #include "mongo/db/pipeline/abt/match_expression_visitor.h" #include "mongo/db/query/ce/ce_histogram.h" #include "mongo/db/query/ce/ce_sampling.h" #include "mongo/db/query/ce/collection_statistics.h" #include "mongo/db/query/ce_mode_parameter.h" +#include "mongo/db/query/cqf_command_utils.h" #include "mongo/db/query/optimizer/cascades/ce_heuristic.h" #include "mongo/db/query/optimizer/cascades/cost_derivation.h" #include "mongo/db/query/optimizer/explain.h" @@ -244,13 +245,14 @@ static QueryHints getHintsFromQueryKnobs() { static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExecutor( OptPhaseManager& phaseManager, - ABT abtTree, + ABT abt, OperationContext* opCtx, boost::intrusive_ptr<ExpressionContext> expCtx, const NamespaceString& nss, - const CollectionPtr& collection) { + const CollectionPtr& collection, + std::unique_ptr<CanonicalQuery> cq) { - const bool optimizationResult = phaseManager.optimize(abtTree); + const bool optimizationResult = phaseManager.optimize(abt); uassert(6624252, "Optimization failed", optimizationResult); { @@ -275,7 +277,7 @@ static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExe OPTIMIZER_DEBUG_LOG(6264801, 5, "Optimized ABT", "explain"_attr = explain); } - auto env = VariableEnvironment::build(abtTree); + auto env = VariableEnvironment::build(abt); SlotVarMap slotMap; sbe::value::SlotIdGenerator ids; SBENodeLowering g{env, @@ -284,7 +286,7 @@ static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExe phaseManager.getMetadata(), phaseManager.getNodeToGroupPropsMap(), phaseManager.getRIDProjections()}; - auto sbePlan = g.optimize(abtTree); + auto sbePlan = g.optimize(abt); uassert(6624253, "Lowering failed: did not produce a plan.", sbePlan != nullptr); uassert(6624254, "Lowering failed: did not produce any output slots.", !slotMap.empty()); @@ -313,10 +315,10 @@ static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExe sbePlan->prepare(data.ctx); auto planExec = uassertStatusOK(plan_executor_factory::make( opCtx, - nullptr /*cq*/, + std::move(cq), nullptr /*solution*/, {std::move(sbePlan), std::move(data)}, - std::make_unique<ABTPrinter>(std::move(abtTree), phaseManager.getNodeToGroupPropsMap()), + std::make_unique<ABTPrinter>(std::move(abt), phaseManager.getNodeToGroupPropsMap()), MultipleCollectionAccessor(collection), QueryPlannerParams::Options::DEFAULT, nss, @@ -324,16 +326,17 @@ static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExe return planExec; } -static void populateAdditionalScanDefs(OperationContext* opCtx, - boost::intrusive_ptr<ExpressionContext> expCtx, - const Pipeline& pipeline, - const boost::optional<BSONObj>& indexHint, - const size_t numberOfPartitions, - PrefixId& prefixId, - opt::unordered_map<std::string, ScanDefinition>& scanDefs, - const DisableIndexOptions disableIndexOptions, - bool& disableScan) { - for (const auto& involvedNss : pipeline.getInvolvedCollections()) { +static void populateAdditionalScanDefs( + OperationContext* opCtx, + boost::intrusive_ptr<ExpressionContext> expCtx, + const stdx::unordered_set<NamespaceString>& involvedCollections, + const boost::optional<BSONObj>& indexHint, + const size_t numberOfPartitions, + PrefixId& prefixId, + opt::unordered_map<std::string, ScanDefinition>& scanDefs, + const DisableIndexOptions disableIndexOptions, + bool& disableScan) { + for (const auto& involvedNss : involvedCollections) { // TODO handle views? AutoGetCollectionForReadCommandMaybeLockFree ctx( opCtx, involvedNss, AutoGetCollectionViewMode::kViewsForbidden); @@ -378,19 +381,10 @@ static void populateAdditionalScanDefs(OperationContext* opCtx, } } -std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( - OperationContext* opCtx, - boost::intrusive_ptr<ExpressionContext> expCtx, - const NamespaceString& nss, - const CollectionPtr& collection, - const boost::optional<BSONObj>& indexHint, - const Pipeline& pipeline) { - const bool collectionExists = collection != nullptr; - const std::string uuidStr = collectionExists ? collection->uuid().toString() : "<missing_uuid>"; - const std::string collNameStr = nss.coll().toString(); - const std::string scanDefName = collNameStr + "_" + uuidStr; - - if (indexHint && !pipeline.getInvolvedCollections().empty()) { +void validateCommandOptions(const CollectionPtr& collection, + const boost::optional<BSONObj>& indexHint, + const stdx::unordered_set<NamespaceString>& involvedCollections) { + if (indexHint && !involvedCollections.empty()) { uasserted(6624256, "For now we can apply hints only for queries involving a single collection"); } @@ -406,14 +400,20 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp uassert(ErrorCodes::InternalErrorNotSupported, "Timeseries collections are not supported", !collection || !collection->getTimeseriesOptions()); +} - auto curOp = CurOp::get(opCtx); - curOp->debug().cqfUsed = true; - - QueryHints queryHints = getHintsFromQueryKnobs(); - - PrefixId prefixId; - const ProjectionName& scanProjName = prefixId.getNextId("scan"); +Metadata populateMetadata(boost::intrusive_ptr<ExpressionContext> expCtx, + const CollectionPtr& collection, + const stdx::unordered_set<NamespaceString>& involvedCollections, + const NamespaceString& nss, + const boost::optional<BSONObj>& indexHint, + const ProjectionName& scanProjName, + const std::string& uuidStr, + const std::string& scanDefName, + QueryHints& queryHints, + PrefixId& prefixId) { + auto opCtx = expCtx->opCtx; + const bool collectionExists = collection != nullptr; // Add the base collection metadata. opt::unordered_map<std::string, optimizer::IndexDefinition> indexDefs; @@ -440,7 +440,7 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp ScanDefinition({{"type", "mongod"}, {"database", nss.db().toString()}, {"uuid", uuidStr}, - {ScanNode::kDefaultCollectionNameSpec, collNameStr}}, + {ScanNode::kDefaultCollectionNameSpec, nss.coll().toString()}}, std::move(indexDefs), std::move(distribution), collectionExists, @@ -450,7 +450,7 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp // been accounted for above and isn't included here. populateAdditionalScanDefs(opCtx, expCtx, - pipeline, + involvedCollections, indexHint, numberOfPartitions, prefixId, @@ -458,16 +458,69 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp queryHints._disableIndexes, queryHints._disableScan); - Metadata metadata(std::move(scanDefs), numberOfPartitions); + return {std::move(scanDefs), numberOfPartitions}; +} - ABT abtTree = collectionExists ? make<ScanNode>(scanProjName, scanDefName) - : make<ValueScanNode>(ProjectionNameVector{scanProjName}); - abtTree = - translatePipelineToABT(metadata, pipeline, scanProjName, std::move(abtTree), prefixId); +std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( + OperationContext* opCtx, + boost::intrusive_ptr<ExpressionContext> expCtx, + const NamespaceString& nss, + const CollectionPtr& collection, + const boost::optional<BSONObj>& indexHint, + std::unique_ptr<Pipeline, PipelineDeleter> pipeline, + std::unique_ptr<CanonicalQuery> canonicalQuery) { + + // Ensure that either pipeline or canonicalQuery is set. + tassert(624070, + "getSBEExecutorViaCascadesOptimizer expects exactly one of the following to be set: " + "canonicalQuery, pipeline", + static_cast<bool>(pipeline) != static_cast<bool>(canonicalQuery)); + + stdx::unordered_set<NamespaceString> involvedCollections; + if (pipeline) { + involvedCollections = pipeline->getInvolvedCollections(); + } + + validateCommandOptions(collection, indexHint, involvedCollections); + + auto curOp = CurOp::get(opCtx); + curOp->debug().cqfUsed = true; + + const bool collectionExists = collection != nullptr; + const std::string uuidStr = collectionExists ? collection->uuid().toString() : "<missing_uuid>"; + const std::string collNameStr = nss.coll().toString(); + const std::string scanDefName = collNameStr + "_" + uuidStr; + PrefixId prefixId; + const ProjectionName& scanProjName = prefixId.getNextId("scan"); + QueryHints queryHints = getHintsFromQueryKnobs(); + + auto metadata = populateMetadata(expCtx, + collection, + involvedCollections, + nss, + indexHint, + scanProjName, + uuidStr, + scanDefName, + queryHints, + prefixId); + + ABT abt = collectionExists ? make<ScanNode>(scanProjName, scanDefName) + : make<ValueScanNode>(ProjectionNameVector{scanProjName}); + + if (pipeline) { + abt = translatePipelineToABT(metadata, *pipeline, scanProjName, std::move(abt), prefixId); + } else { + abt = translateCanonicalQueryToABT( + metadata, *canonicalQuery, scanProjName, std::move(abt), prefixId); + } OPTIMIZER_DEBUG_LOG( - 6264803, 5, "Translated ABT", "explain"_attr = ExplainGenerator::explainV2(abtTree)); + 6264803, 5, "Translated ABT", "explain"_attr = ExplainGenerator::explainV2(abt)); + + const int64_t numRecords = collectionExists ? collection->numRecords(opCtx) : -1; + // TODO SERVER-68919: Move OptPhaseManager construction to its own function. if (internalQueryCardinalityEstimatorMode == ce::kSampling && collectionExists && numRecords > 0) { Metadata metadataForSampling = metadata; @@ -483,7 +536,8 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp std::move(metadataForSampling), std::make_unique<HeuristicCE>(), std::make_unique<DefaultCosting>(), - DebugInfo::kDefaultForProd); + DebugInfo::kDefaultForProd, + {}); OptPhaseManager phaseManager{ OptPhaseManager::getAllRewritesSet(), @@ -492,11 +546,16 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp std::move(metadata), std::make_unique<CESamplingTransport>(opCtx, phaseManagerForSampling, numRecords), std::make_unique<DefaultCosting>(), - DebugInfo::kDefaultForProd}; - phaseManager.getHints() = queryHints; + DebugInfo::kDefaultForProd, + std::move(queryHints)}; - return optimizeAndCreateExecutor( - phaseManager, std::move(abtTree), opCtx, expCtx, nss, collection); + return optimizeAndCreateExecutor(phaseManager, + std::move(abt), + opCtx, + expCtx, + nss, + collection, + std::move(canonicalQuery)); } else if (internalQueryCardinalityEstimatorMode == ce::kHistogram && ce::CollectionStatistics::hasCollectionStatistics(nss)) { @@ -508,22 +567,42 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp std::move(metadata), std::move(ceDerivation), std::make_unique<DefaultCosting>(), - DebugInfo::kDefaultForProd}; + DebugInfo::kDefaultForProd, + std::move(queryHints)}; + + return optimizeAndCreateExecutor(phaseManager, + std::move(abt), + opCtx, + expCtx, + nss, + collection, + std::move(canonicalQuery)); + } + // Default to using heuristics. + OptPhaseManager phaseManager{OptPhaseManager::getAllRewritesSet(), + prefixId, + std::move(metadata), + DebugInfo::kDefaultForProd, + std::move(queryHints)}; + + return optimizeAndCreateExecutor( + phaseManager, std::move(abt), opCtx, expCtx, nss, collection, std::move(canonicalQuery)); +} - return optimizeAndCreateExecutor( - phaseManager, std::move(abtTree), opCtx, expCtx, nss, collection); +std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( + const CollectionPtr& collection, std::unique_ptr<CanonicalQuery> query) { - } else { - // Default to using heuristics. - OptPhaseManager phaseManager{OptPhaseManager::getAllRewritesSet(), - prefixId, - std::move(metadata), - DebugInfo::kDefaultForProd}; - phaseManager.getHints() = queryHints; + boost::optional<BSONObj> indexHint = query->getFindCommandRequest().getHint().isEmpty() + ? boost::none + : boost::make_optional(query->getFindCommandRequest().getHint()); - return optimizeAndCreateExecutor( - phaseManager, std::move(abtTree), opCtx, expCtx, nss, collection); - } + + auto opCtx = query->getOpCtx(); + auto expCtx = query->getExpCtx(); + auto nss = query->nss(); + + return getSBEExecutorViaCascadesOptimizer( + opCtx, expCtx, nss, collection, indexHint, nullptr /* pipeline */, std::move(query)); } } // namespace mongo diff --git a/src/mongo/db/query/cqf_get_executor.h b/src/mongo/db/query/cqf_get_executor.h new file mode 100644 index 00000000000..81454fad196 --- /dev/null +++ b/src/mongo/db/query/cqf_get_executor.h @@ -0,0 +1,60 @@ +/** + * 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/db/catalog/collection.h" +#include "mongo/db/pipeline/pipeline.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/plan_executor.h" + +namespace mongo { + +/** + * Returns a PlanExecutor for the given Pipeline. + * + * The CanonicalQuery parameter allows for code reuse between functions in this file and should not + * be set by callers. + */ +std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( + OperationContext* opCtx, + boost::intrusive_ptr<ExpressionContext> expCtx, + const NamespaceString& nss, + const CollectionPtr& collection, + const boost::optional<BSONObj>& indexHint, + std::unique_ptr<Pipeline, PipelineDeleter> pipeline, + std::unique_ptr<CanonicalQuery> = nullptr); + +/** + * Returns a PlanExecutor for the given CanonicalQuery. + */ +std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOptimizer( + const CollectionPtr& collection, std::unique_ptr<CanonicalQuery> query); + +} // namespace mongo diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 670fd05ff08..26ef055f60e 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -71,6 +71,8 @@ #include "mongo/db/query/collation/collation_index_key.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/cqf_command_utils.h" +#include "mongo/db/query/cqf_get_executor.h" #include "mongo/db/query/explain.h" #include "mongo/db/query/index_bounds_builder.h" #include "mongo/db/query/internal_plans.h" @@ -1434,6 +1436,10 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor( canonicalQuery->setSbeCompatible( sbe::isQuerySbeCompatible(&mainColl, canonicalQuery.get(), plannerParams.options)); + if (isEligibleForBonsai(*canonicalQuery, opCtx, mainColl)) { + return getSBEExecutorViaCascadesOptimizer(mainColl, std::move(canonicalQuery)); + } + // Use SBE if 'canonicalQuery' is SBE compatible. if (!canonicalQuery->getForceClassicEngine() && canonicalQuery->isSbeCompatible()) { if (extractAndAttachPipelineStages) { diff --git a/src/mongo/db/query/optimizer/cascades/memo.h b/src/mongo/db/query/optimizer/cascades/memo.h index c41d46f8f37..7038f236634 100644 --- a/src/mongo/db/query/optimizer/cascades/memo.h +++ b/src/mongo/db/query/optimizer/cascades/memo.h @@ -187,6 +187,12 @@ public: std::unique_ptr<LogicalPropsInterface> logicalPropsDerivation, std::unique_ptr<CEInterface> ceDerivation); + // TODO SERVER-68914: Fix object ownership issues of data members of the Memo class. + Memo(const Memo&) = delete; + Memo& operator=(const Memo&) = delete; + Memo(Memo&&) = delete; + Memo& operator=(Memo&&) = delete; + const Group& getGroup(GroupIdType groupId) const; Group& getGroup(GroupIdType groupId); size_t getGroupCount() const; diff --git a/src/mongo/db/query/optimizer/opt_phase_manager.cpp b/src/mongo/db/query/optimizer/opt_phase_manager.cpp index 17e38eecad4..e7d862f25a3 100644 --- a/src/mongo/db/query/optimizer/opt_phase_manager.cpp +++ b/src/mongo/db/query/optimizer/opt_phase_manager.cpp @@ -50,14 +50,16 @@ OptPhaseManager::PhaseSet OptPhaseManager::_allRewrites = {OptPhase::ConstEvalPr OptPhaseManager::OptPhaseManager(OptPhaseManager::PhaseSet phaseSet, PrefixId& prefixId, Metadata metadata, - DebugInfo debugInfo) + DebugInfo debugInfo, + QueryHints queryHints) : OptPhaseManager(std::move(phaseSet), prefixId, false /*requireRID*/, std::move(metadata), std::make_unique<HeuristicCE>(), std::make_unique<DefaultCosting>(), - std::move(debugInfo)) {} + std::move(debugInfo), + std::move(queryHints)) {} OptPhaseManager::OptPhaseManager(OptPhaseManager::PhaseSet phaseSet, PrefixId& prefixId, @@ -65,10 +67,11 @@ OptPhaseManager::OptPhaseManager(OptPhaseManager::PhaseSet phaseSet, Metadata metadata, std::unique_ptr<CEInterface> ceDerivation, std::unique_ptr<CostingInterface> costDerivation, - DebugInfo debugInfo) + DebugInfo debugInfo, + QueryHints queryHints) : _phaseSet(std::move(phaseSet)), _debugInfo(std::move(debugInfo)), - _hints(), + _hints(std::move(queryHints)), _metadata(std::move(metadata)), _memo(_debugInfo, _metadata, diff --git a/src/mongo/db/query/optimizer/opt_phase_manager.h b/src/mongo/db/query/optimizer/opt_phase_manager.h index b06742be250..b81d7845c2d 100644 --- a/src/mongo/db/query/optimizer/opt_phase_manager.h +++ b/src/mongo/db/query/optimizer/opt_phase_manager.h @@ -70,14 +70,25 @@ public: using PhaseSet = opt::unordered_set<OptPhase>; - OptPhaseManager(PhaseSet phaseSet, PrefixId& prefixId, Metadata metadata, DebugInfo debugInfo); + OptPhaseManager(PhaseSet phaseSet, + PrefixId& prefixId, + Metadata metadata, + DebugInfo debugInfo, + QueryHints queryHints = {}); OptPhaseManager(PhaseSet phaseSet, PrefixId& prefixId, bool requireRID, Metadata metadata, std::unique_ptr<CEInterface> ceDerivation, std::unique_ptr<CostingInterface> costDerivation, - DebugInfo debugInfo); + DebugInfo debugInfo, + QueryHints queryHints = {}); + + // TODO SERVER-68914: Fix object ownership issues of data members of the Memo class. + OptPhaseManager(const OptPhaseManager&) = delete; + OptPhaseManager& operator=(const OptPhaseManager&) = delete; + OptPhaseManager(OptPhaseManager&&) = delete; + OptPhaseManager& operator=(OptPhaseManager&&) = delete; /** * Optimization modifies the input argument. |