diff options
-rw-r--r-- | buildscripts/resmokeconfig/suites/cqf.yml | 2 | ||||
-rw-r--r-- | buildscripts/resmokeconfig/suites/cqf_parallel.yml | 2 | ||||
-rw-r--r-- | jstests/cqf/match_with_in.js | 6 | ||||
-rw-r--r-- | jstests/libs/optimizer_utils.js | 22 | ||||
-rw-r--r-- | jstests/noPassthrough/cqf_fallback.js | 204 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/cqf/cqf_aggregate.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/commands/cqf/cqf_command_utils.cpp | 696 | ||||
-rw-r--r-- | src/mongo/db/commands/cqf/cqf_command_utils.h | 53 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/commands/run_aggregate.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/pipeline/abt/match_expression_visitor.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/pipeline/visitors/document_source_visitor.h | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/visitors/document_source_walker.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/query_feature_flags.idl | 2 | ||||
-rw-r--r-- | src/mongo/db/query/query_knobs.idl | 10 |
17 files changed, 1053 insertions, 17 deletions
diff --git a/buildscripts/resmokeconfig/suites/cqf.yml b/buildscripts/resmokeconfig/suites/cqf.yml index 5c4415228b7..bbf84fdf079 100644 --- a/buildscripts/resmokeconfig/suites/cqf.yml +++ b/buildscripts/resmokeconfig/suites/cqf.yml @@ -28,3 +28,5 @@ executor: enableTestCommands: 1 featureFlagCommonQueryFramework: true internalQueryEnableCascadesOptimizer: true + # This flag disables the fallback path that may hide bugs in CQF. + internalQueryForceCommonQueryFramework: true diff --git a/buildscripts/resmokeconfig/suites/cqf_parallel.yml b/buildscripts/resmokeconfig/suites/cqf_parallel.yml index 57d55f023a3..b8463c94fa5 100644 --- a/buildscripts/resmokeconfig/suites/cqf_parallel.yml +++ b/buildscripts/resmokeconfig/suites/cqf_parallel.yml @@ -28,4 +28,6 @@ executor: enableTestCommands: 1 featureFlagCommonQueryFramework: true internalQueryEnableCascadesOptimizer: true + # This flag disables the fallback path that may hide bugs in CQF. + internalQueryForceCommonQueryFramework: true internalQueryDefaultDOP: 5 diff --git a/jstests/cqf/match_with_in.js b/jstests/cqf/match_with_in.js index 788f91d99f9..58909bd8775 100644 --- a/jstests/cqf/match_with_in.js +++ b/jstests/cqf/match_with_in.js @@ -3,6 +3,7 @@ */ load('jstests/aggregation/extras/utils.js'); // For assertArrayEq. +load('jstests/libs/optimizer_utils.js'); (function() { "use strict"; @@ -37,6 +38,11 @@ const runTest = (filter, expected) => { const result = coll.aggregate({$match: filter}).toArray(); assertArrayEq( {actual: result, expected: expected, extraErrorMsg: tojson({filter: filter})}); + + // Sanity check that the query uses the bonsai optimizer. + const explain = assert.commandWorked(db.runCommand( + {explain: {aggregate: coll.getName(), pipeline: [{$match: filter}], cursor: {}}})); + assert(usedBonsaiOptimizer(explain), tojson(explain)); } finally { assert.commandWorked( db.adminCommand({'configureFailPoint': 'disablePipelineOptimization', 'mode': 'off'})); diff --git a/jstests/libs/optimizer_utils.js b/jstests/libs/optimizer_utils.js index ff2a179388a..35346e02c67 100644 --- a/jstests/libs/optimizer_utils.js +++ b/jstests/libs/optimizer_utils.js @@ -1,3 +1,5 @@ +load("jstests/libs/analyze_plan.js"); + /* * Utility for checking if the query optimizer is enabled. */ @@ -6,3 +8,23 @@ function checkCascadesOptimizerEnabled(theDB) { return param.hasOwnProperty("featureFlagCommonQueryFramework") && param.featureFlagCommonQueryFramework.value; } + +/** + * 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; + } + + const plannerOutput = getAggPlanStage(explain, "$cursor"); + if (plannerOutput != null) { + return plannerOutput["$cursor"].queryPlanner.winningPlan.hasOwnProperty("optimizerPlan"); + } else { + return explain.queryPlanner.winningPlan.hasOwnProperty("optimizerPlan"); + } +}
\ No newline at end of file diff --git a/jstests/noPassthrough/cqf_fallback.js b/jstests/noPassthrough/cqf_fallback.js new file mode 100644 index 00000000000..101a9fe4309 --- /dev/null +++ b/jstests/noPassthrough/cqf_fallback.js @@ -0,0 +1,204 @@ +/** + * Verify that expressions and operators are correctly routed to CQF where eligible. This decision + * is based on several factors including the query text, collection metadata, etc.. + */ +(function() { +"use strict"; + +load("jstests/libs/analyze_plan.js"); +load("jstests/libs/optimizer_utils.js"); + +let conn = MongoRunner.runMongod({setParameter: {featureFlagCommonQueryFramework: true}}); +assert.neq(null, conn, "mongod was unable to start up"); + +let db = conn.getDB("test"); +let coll = db[jsTestName()]; +coll.drop(); + +function assertUsesFallback(cmd, testOnly) { + // An unsupported stage should not use the new optimizer. + assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceCommonQueryFramework: false})); + const defaultExplain = assert.commandWorked(db.runCommand({explain: cmd})); + assert(!usedBonsaiOptimizer(defaultExplain), tojson(defaultExplain)); + + // Non-explain should also work and use the fallback mechanism, but we cannnot verify exactly + // this without looking at the logs. + assert.commandWorked(db.runCommand(cmd)); + + // Force the bonsai optimizer and expect the query to either fail if unsupported, or pass if + // marked as "test only". + assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceCommonQueryFramework: true})); + if (testOnly) { + const explain = assert.commandWorked(db.runCommand({explain: cmd})); + assert(usedBonsaiOptimizer(explain), tojson(explain)); + } else { + assert.commandFailedWithCode(db.runCommand(cmd), ErrorCodes.InternalErrorNotSupported); + } + + // Forcing the classic engine should override the CQF flag. + { + assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceClassicEngine: true})); + const explain = assert.commandWorked(db.runCommand({explain: cmd})); + assert(!usedBonsaiOptimizer(explain), tojson(explain)); + assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceClassicEngine: false})); + } +} + +// Unsupported aggregation stage. +assertUsesFallback({aggregate: coll.getName(), pipeline: [{$sample: {size: 1}}], cursor: {}}, + false); + +// Test-only aggregation stage. +assertUsesFallback( + {aggregate: coll.getName(), pipeline: [{$group: {_id: null, a: {$sum: "$b"}}}], cursor: {}}, + true); + +// Unsupported match expression. +assertUsesFallback({find: coll.getName(), filter: {a: {$mod: [4, 0]}}}, false); +assertUsesFallback( + {aggregate: coll.getName(), pipeline: [{$match: {a: {$mod: [4, 0]}}}], cursor: {}}, false); +assertUsesFallback({find: coll.getName(), filter: {a: {$in: [/^b/, 1]}}}, false); + +// Test-only match expression. +assertUsesFallback({find: coll.getName(), filter: {$alwaysFalse: 1}}, true); +assertUsesFallback({aggregate: coll.getName(), pipeline: [{$match: {$alwaysFalse: 1}}], cursor: {}}, + true); + +// Unsupported projection expression. +assertUsesFallback( + {find: coll.getName(), filter: {}, projection: {a: {$concatArrays: [["$b"], ["suppported"]]}}}, + false); +assertUsesFallback({ + aggregate: coll.getName(), + pipeline: [{$project: {a: {$concatArrays: [["$b"], ["suppported"]]}}}], + cursor: {} +}, + false); + +// Test-only projection spec. +assertUsesFallback( + {find: coll.getName(), filter: {}, projection: {a: {$concat: ["test", "-only"]}}}, true); +assertUsesFallback({ + aggregate: coll.getName(), + pipeline: [{$project: {a: {$concat: ["test", "-only"]}}}], + cursor: {} +}, + true); + +// Numeric path components are not supported, either in a match expression or projection. +assertUsesFallback({find: coll.getName(), filter: {'a.0': 5}}); +assertUsesFallback({find: coll.getName(), filter: {'a.0.b': 5}}); +assertUsesFallback({find: coll.getName(), filter: {}, projection: {'a.0': 1}}); +assertUsesFallback({find: coll.getName(), filter: {}, projection: {'a.5.c': 0}}); + +// Test for unsupported expressions within a branching expression such as $or. +assertUsesFallback({find: coll.getName(), filter: {$or: [{'a.0': 5}, {a: 1}]}}); +assertUsesFallback({find: coll.getName(), filter: {$or: [{a: 5}, {a: {$mod: [4, 0]}}]}}); + +// Unsupported command options. +assertUsesFallback({find: coll.getName(), filter: {}, collation: {locale: "fr_CA"}}, true); +assertUsesFallback({ + aggregate: coll.getName(), + pipeline: [{$match: {$alwaysFalse: 1}}], + collation: {locale: "fr_CA"}, + cursor: {} +}, + true); + +// Unsupported index type. +assert.commandWorked(coll.createIndex({a: 1}, {sparse: true})); +assertUsesFallback({find: coll.getName(), filter: {}}); +assertUsesFallback({aggregate: coll.getName(), pipeline: [], cursor: {}}); +coll.drop(); +assert.commandWorked(coll.insert({a: 1})); +assert.commandWorked(coll.createIndex({"$**": 1})); +assertUsesFallback({find: coll.getName(), filter: {}}); +assertUsesFallback({aggregate: coll.getName(), pipeline: [], cursor: {}}); + +// Test-only index type. +coll.drop(); +assert.commandWorked(coll.insert({a: 1})); +assert.commandWorked(coll.createIndex({a: 1}, {partialFilterExpression: {a: {$gt: 0}}})); +assertUsesFallback({find: coll.getName(), filter: {}}, true); +assertUsesFallback({aggregate: coll.getName(), pipeline: [], cursor: {}}, true); + +// Unsupported collection types. Note that a query against the user-facing timeseries collection +// will fail due to the unsupported $unpackBucket stage. +coll.drop(); +assert.commandWorked(db.createCollection(coll.getName(), {timeseries: {timeField: "time"}})); +assertUsesFallback({find: coll.getName(), filter: {}}, false); +assertUsesFallback({aggregate: coll.getName(), pipeline: [], cursor: {}}, false); + +const bucketColl = db.getCollection('system.buckets.' + coll.getName()); +assertUsesFallback({find: bucketColl.getName(), filter: {}}, false); +assertUsesFallback({aggregate: bucketColl.getName(), pipeline: [], cursor: {}}, false); + +// Collection-default collation is not supported if non-simple. +coll.drop(); +assert.commandWorked(db.createCollection(coll.getName(), {collation: {locale: "fr_CA"}})); +assertUsesFallback({find: coll.getName(), filter: {}}, false); +assertUsesFallback({aggregate: coll.getName(), pipeline: [], cursor: {}}, false); + +// Queries over views are supported as long as the resolved pipeline is valid in CQF. +coll.drop(); +assert.commandWorked(coll.insert({a: 1})); +assert.commandWorked( + db.runCommand({create: "view", viewOn: coll.getName(), pipeline: [{$match: {a: 1}}]})); + +// Unsupported expression on top of the view. +assertUsesFallback({find: "view", filter: {a: {$mod: [4, 0]}}}, false); + +// Supported expression on top of the view. +assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceCommonQueryFramework: true})); +assert.commandWorked(db.runCommand({find: "view", filter: {b: 4}})); + +// Test-only expression on top of a view. +assertUsesFallback({find: "view", filter: {$alwaysFalse: 1}}, true); + +// Create a view with an unsupported expression. +assert.commandWorked(db.runCommand( + {create: "invalidView", viewOn: coll.getName(), pipeline: [{$match: {a: {$mod: [4, 0]}}}]})); + +// Any expression, supported or not, should not use CQF over the invalid view. +assertUsesFallback({find: "invalidView", filter: {b: 4}}, false); + +// Test only expression should also fail. +assertUsesFallback({find: "invalidView", filter: {$alwaysFalse: 1}}, true); + +MongoRunner.stopMongod(conn); + +// Restart the mongod and verify that we never use the bonsai optimizer if the feature flag is not +// set. +conn = MongoRunner.runMongod(); +assert.neq(null, conn, "mongod was unable to start up"); + +db = conn.getDB("test"); +coll = db[jsTestName()]; +coll.drop(); + +const supportedExpression = { + a: {$eq: 4} +}; + +let explain = coll.explain().find(supportedExpression).finish(); +assert(!usedBonsaiOptimizer(explain), tojson(explain)); + +explain = coll.explain().aggregate([{$match: supportedExpression}]); +assert(!usedBonsaiOptimizer(explain), tojson(explain)); + +// Setting the force CQF flag has no effect. +assert.commandWorked( + db.adminCommand({setParameter: 1, internalQueryForceCommonQueryFramework: true})); +explain = coll.explain().find(supportedExpression).finish(); +assert(!usedBonsaiOptimizer(explain), tojson(explain)); + +explain = coll.explain().aggregate([{$match: supportedExpression}]); +assert(!usedBonsaiOptimizer(explain), tojson(explain)); + +MongoRunner.stopMongod(conn); +}());
\ No newline at end of file diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 248b76aa534..a9a5e95533e 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -326,6 +326,7 @@ env.Library( source=[ "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/cqf/cqf_aggregate.cpp b/src/mongo/db/commands/cqf/cqf_aggregate.cpp index aabfc99c3a5..26f6bcf5cff 100644 --- a/src/mongo/db/commands/cqf/cqf_aggregate.cpp +++ b/src/mongo/db/commands/cqf/cqf_aggregate.cpp @@ -87,15 +87,10 @@ static opt::unordered_map<std::string, optimizer::IndexDefinition> buildIndexSpe while (indexIterator->more()) { const IndexCatalogEntry& catalogEntry = *indexIterator->next(); - const bool isMultiKey = catalogEntry.isMultikey(opCtx, collection); - const MultikeyPaths& multiKeyPaths = catalogEntry.getMultikeyPaths(opCtx, collection); - uassert(6624251, "Multikey paths cannot be empty.", !multiKeyPaths.empty()); - const IndexDescriptor& descriptor = *catalogEntry.descriptor(); if (descriptor.hidden() || descriptor.isSparse() || descriptor.getIndexType() != IndexType::INDEX_BTREE) { - // Not supported for now. - continue; + uasserted(ErrorCodes::InternalErrorNotSupported, "Unsupported index type"); } if (indexHint) { @@ -111,6 +106,10 @@ static opt::unordered_map<std::string, optimizer::IndexDefinition> buildIndexSpe } } + const bool isMultiKey = catalogEntry.isMultikey(opCtx, collection); + const MultikeyPaths& multiKeyPaths = catalogEntry.getMultikeyPaths(opCtx, collection); + uassert(6624251, "Multikey paths cannot be empty.", !multiKeyPaths.empty()); + // SBE version is base 0. const int64_t version = static_cast<int>(descriptor.version()) - 1; @@ -380,6 +379,18 @@ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> getSBEExecutorViaCascadesOp uasserted(6624256, "For now we can apply hints only for queries involving a single collection"); } + // Unsupported command/collection options. + uassert(ErrorCodes::InternalErrorNotSupported, + "Collection-default collation is not supported", + !collection || collection->getCollectionOptions().collation.isEmpty()); + + uassert(ErrorCodes::InternalErrorNotSupported, + "Clustered collections are not supported", + !collection || !collection->isClustered()); + + uassert(ErrorCodes::InternalErrorNotSupported, + "Timeseries collections are not supported", + !collection || !collection->getTimeseriesOptions()); QueryHints queryHints = getHintsFromQueryKnobs(); diff --git a/src/mongo/db/commands/cqf/cqf_command_utils.cpp b/src/mongo/db/commands/cqf/cqf_command_utils.cpp new file mode 100644 index 00000000000..2edf7a56772 --- /dev/null +++ b/src/mongo/db/commands/cqf/cqf_command_utils.cpp @@ -0,0 +1,696 @@ +/** + * 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/commands/cqf/cqf_command_utils.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/exec/sbe/abt/abt_lower.h" +#include "mongo/db/matcher/expression_always_boolean.h" +#include "mongo/db/matcher/expression_array.h" +#include "mongo/db/matcher/expression_expr.h" +#include "mongo/db/matcher/expression_geo.h" +#include "mongo/db/matcher/expression_internal_bucket_geo_within.h" +#include "mongo/db/matcher/expression_internal_expr_comparison.h" +#include "mongo/db/matcher/expression_leaf.h" +#include "mongo/db/matcher/expression_text.h" +#include "mongo/db/matcher/expression_text_noop.h" +#include "mongo/db/matcher/expression_tree.h" +#include "mongo/db/matcher/expression_type.h" +#include "mongo/db/matcher/expression_visitor.h" +#include "mongo/db/matcher/expression_where.h" +#include "mongo/db/matcher/expression_where_noop.h" +#include "mongo/db/matcher/match_expression_walker.h" +#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h" +#include "mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_cond.h" +#include "mongo/db/matcher/schema/expression_internal_schema_eq.h" +#include "mongo/db/matcher/schema/expression_internal_schema_fmod.h" +#include "mongo/db/matcher/schema/expression_internal_schema_match_array_index.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_object_match.h" +#include "mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h" +#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_xor.h" +#include "mongo/db/pipeline/abt/abt_document_source_visitor.h" +#include "mongo/db/pipeline/abt/agg_expression_visitor.h" +#include "mongo/db/pipeline/abt/match_expression_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" +#include "mongo/db/pipeline/document_source_current_op.h" +#include "mongo/db/pipeline/document_source_cursor.h" +#include "mongo/db/pipeline/document_source_exchange.h" +#include "mongo/db/pipeline/document_source_facet.h" +#include "mongo/db/pipeline/document_source_geo_near.h" +#include "mongo/db/pipeline/document_source_geo_near_cursor.h" +#include "mongo/db/pipeline/document_source_graph_lookup.h" +#include "mongo/db/pipeline/document_source_group.h" +#include "mongo/db/pipeline/document_source_index_stats.h" +#include "mongo/db/pipeline/document_source_internal_inhibit_optimization.h" +#include "mongo/db/pipeline/document_source_internal_shard_filter.h" +#include "mongo/db/pipeline/document_source_internal_split_pipeline.h" +#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h" +#include "mongo/db/pipeline/document_source_limit.h" +#include "mongo/db/pipeline/document_source_list_cached_and_active_users.h" +#include "mongo/db/pipeline/document_source_list_local_sessions.h" +#include "mongo/db/pipeline/document_source_list_sessions.h" +#include "mongo/db/pipeline/document_source_lookup.h" +#include "mongo/db/pipeline/document_source_match.h" +#include "mongo/db/pipeline/document_source_merge.h" +#include "mongo/db/pipeline/document_source_operation_metrics.h" +#include "mongo/db/pipeline/document_source_out.h" +#include "mongo/db/pipeline/document_source_plan_cache_stats.h" +#include "mongo/db/pipeline/document_source_queue.h" +#include "mongo/db/pipeline/document_source_redact.h" +#include "mongo/db/pipeline/document_source_replace_root.h" +#include "mongo/db/pipeline/document_source_sample.h" +#include "mongo/db/pipeline/document_source_sample_from_random_cursor.h" +#include "mongo/db/pipeline/document_source_sequential_document_cache.h" +#include "mongo/db/pipeline/document_source_single_document_transformation.h" +#include "mongo/db/pipeline/document_source_skip.h" +#include "mongo/db/pipeline/document_source_sort.h" +#include "mongo/db/pipeline/document_source_tee_consumer.h" +#include "mongo/db/pipeline/document_source_union_with.h" +#include "mongo/db/pipeline/document_source_unwind.h" +#include "mongo/db/pipeline/visitors/document_source_visitor.h" +#include "mongo/db/pipeline/visitors/document_source_walker.h" +#include "mongo/db/pipeline/visitors/transformer_interface_walker.h" +#include "mongo/db/query/query_feature_flags_gen.h" +#include "mongo/db/query/query_knobs_gen.h" +#include "mongo/db/query/query_planner_params.h" +#include "mongo/s/query/document_source_merge_cursors.h" + +namespace mongo { + +using namespace optimizer; + +namespace { + +/** + * Visitor that is responsible for indicating whether a MatchExpression is eligible for Bonsai by + * setting the '_eligible' member variable. Expressions which are "test-only" and not officially + * supported should set _eligible to false. + */ +class ABTMatchExpressionVisitor : public MatchExpressionConstVisitor { +public: + ABTMatchExpressionVisitor(bool& eligible) : _eligible(eligible) {} + + void visit(const LTEMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const LTMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const ElemMatchObjectMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const ElemMatchValueMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const EqualityMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const GTEMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const GTMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const InMatchExpression* expr) override { + assertSupportedPathExpression(expr); + + // $in over a regex predicate is not supported. + if (!expr->getRegexes().empty()) { + _eligible = false; + } + } + void visit(const ExistsMatchExpression* expr) override { + assertSupportedPathExpression(expr); + } + void visit(const AndMatchExpression* expr) override {} + void visit(const OrMatchExpression* expr) override {} + + void visit(const GeoMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const GeoNearMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalBucketGeoWithinMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalExprEqMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalExprGTMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalExprGTEMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalExprLTMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalExprLTEMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaAllElemMatchFromIndexMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaAllowedPropertiesMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaBinDataEncryptedTypeExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaBinDataFLE2EncryptedTypeExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaBinDataSubTypeExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaCondMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaEqMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaFmodMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMatchArrayIndexMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMaxItemsMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMaxLengthMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMaxPropertiesMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMinItemsMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMinLengthMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaMinPropertiesMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaObjectMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaRootDocEqMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaTypeExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaUniqueItemsMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const InternalSchemaXorMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const ModMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const NorMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const NotMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const RegexMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const SizeMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const TextMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const TextNoOpMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const TwoDPtInAnnulusExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const WhereMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const WhereNoOpMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const BitsAllClearMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const BitsAllSetMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const BitsAnyClearMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const BitsAnySetMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const TypeMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const AlwaysFalseMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const AlwaysTrueMatchExpression* expr) override { + unsupportedExpression(expr); + } + + void visit(const ExprMatchExpression* expr) override { + unsupportedExpression(expr); + } + +private: + void unsupportedExpression(const MatchExpression* expr) { + _eligible = false; + } + + void assertSupportedPathExpression(const PathMatchExpression* expr) { + if (FieldRef(expr->path()).hasNumericPathComponents()) + _eligible = false; + } + + bool& _eligible; +}; + + +class ABTTransformerVisitor : public TransformerInterfaceConstVisitor { +public: + ABTTransformerVisitor(bool& eligible) : _eligible(eligible) {} + + void visit(const projection_executor::ExclusionProjectionExecutor* transformer) override { + std::set<std::string> preservedPaths; + transformer->getRoot()->reportProjectedPaths(&preservedPaths); + + for (const std::string& path : preservedPaths) { + if (FieldRef(path).hasNumericPathComponents()) { + unsupportedTransformer(transformer); + return; + } + } + } + + void visit(const projection_executor::InclusionProjectionExecutor* transformer) override { + std::set<std::string> computedPaths; + StringMap<std::string> renamedPaths; + transformer->getRoot()->reportComputedPaths(&computedPaths, &renamedPaths); + + // Non-simple projections are supported under test only. + if (computedPaths.size() > 0 || renamedPaths.size() > 0) { + unsupportedTransformer(transformer); + return; + } + + std::set<std::string> preservedPaths; + transformer->getRoot()->reportProjectedPaths(&preservedPaths); + + for (const std::string& path : preservedPaths) { + if (FieldRef(path).hasNumericPathComponents()) { + unsupportedTransformer(transformer); + return; + } + } + } + + void visit(const projection_executor::AddFieldsProjectionExecutor* transformer) override { + unsupportedTransformer(transformer); + } + + void visit(const GroupFromFirstDocumentTransformation* transformer) override { + unsupportedTransformer(transformer); + } + + void visit(const ReplaceRootTransformation* transformer) override { + unsupportedTransformer(transformer); + } + +private: + void unsupportedTransformer(const TransformerInterface* transformer) const { + _eligible = false; + } + + bool& _eligible; +}; + +/** + * Visitor that is responsible for indicating whether a DocumentSource is eligible for Bonsai by + * setting the 'eligible' member variable. Stages which are "test-only" and not officially supported + * should set 'eligible' to false. + */ +class ABTUnsupportedDocumentSourceVisitor : public DocumentSourceConstVisitor { +public: + void visit(const DocumentSourceInternalUnpackBucket* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceBucketAuto* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceCollStats* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceCurrentOp* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceCursor* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceExchange* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceFacet* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceGeoNear* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceGeoNearCursor* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceGraphLookUp* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceIndexStats* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceInternalShardFilter* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceInternalSplitPipeline* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceListCachedAndActiveUsers* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceListLocalSessions* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceListSessions* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceLookUp* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceMerge* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceMergeCursors* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceOperationMetrics* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceOut* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourcePlanCacheStats* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceQueue* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceRedact* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceSample* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceSampleFromRandomCursor* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceSequentialDocumentCache* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceTeeConsumer* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceGroup* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceLimit* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceSkip* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceSort* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceUnwind* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceUnionWith* source) override { + unsupportedStage(source); + } + + void visit(const DocumentSourceInternalInhibitOptimization* source) override { + // Can be ignored. + } + + void visit(const DocumentSourceMatch* source) override { + // Pass a reference to our local 'eligible' variable to allow the visitor to overwrite it. + ABTMatchExpressionVisitor visitor(eligible); + MatchExpressionWalker walker(nullptr /*preVisitor*/, nullptr /*inVisitor*/, &visitor); + tree_walker::walk<true, MatchExpression>(source->getMatchExpression(), &walker); + } + + void visit(const DocumentSourceSingleDocumentTransformation* source) override { + ABTTransformerVisitor visitor(eligible); + TransformerInterfaceWalker walker(&visitor); + walker.walk(&source->getTransformer()); + } + + void unsupportedStage(const DocumentSource* source) { + eligible = false; + } + + bool eligible = true; +}; + +template <class RequestType> +bool isEligibleCommon(const RequestType& request, + OperationContext* opCtx, + const CollectionPtr& collection) { + // The FindCommandRequest defaults some parameters to BSONObj() instead of boost::none. + auto noneOrDefaultEmpty = [&](auto param) { + if constexpr (std::is_same_v<decltype(param), boost::optional<BSONObj>>) { + return param && !param->isEmpty(); + } else { + return !param.isEmpty(); + } + }; + bool unsupportedCmdOption = noneOrDefaultEmpty(request.getHint()) || + noneOrDefaultEmpty(request.getCollation()) || request.getLet() || + request.getLegacyRuntimeConstants(); + + bool unsupportedIndexType = [&]() { + if (collection == nullptr) + return false; + + const IndexCatalog& indexCatalog = *collection->getIndexCatalog(); + auto indexIterator = + indexCatalog.getIndexIterator(opCtx, IndexCatalog::InclusionPolicy::kReady); + + while (indexIterator->more()) { + const IndexDescriptor& descriptor = *indexIterator->next()->descriptor(); + if (descriptor.isPartial() || descriptor.hidden() || descriptor.isSparse() || + descriptor.getIndexType() != IndexType::INDEX_BTREE) { + return true; + } + } + return false; + }(); + + bool unsupportedCollectionType = [&]() { + if (collection == nullptr) + return false; + + if (collection->isClustered() || !collection->getCollectionOptions().collation.isEmpty() || + collection->getTimeseriesOptions()) { + return true; + } + + return false; + }(); + + return !unsupportedCmdOption && !unsupportedIndexType && !unsupportedCollectionType; +} + +boost::optional<bool> shouldForceBonsai() { + // Without the feature flag set, nothing else matters. + if (!feature_flags::gFeatureFlagCommonQueryFramework.isEnabled( + serverGlobalParams.featureCompatibility)) { + return false; + } + + // The "force classic" flag takes precedence over the others. + if (internalQueryForceClassicEngine.load()) { + return false; + } + + if (internalQueryForceCommonQueryFramework.load()) { + return true; + } + + if (!internalQueryEnableCascadesOptimizer.load()) { + return false; + } + + return boost::none; +} + +} // namespace + +bool isEligibleForBonsai(const AggregateCommandRequest& request, + const Pipeline& pipeline, + OperationContext* opCtx, + const CollectionPtr& collection) { + if (auto forceBonsai = shouldForceBonsai(); forceBonsai.has_value()) { + return *forceBonsai; + } + + bool commandOptionsEligible = isEligibleCommon(request, opCtx, collection) && + !request.getUnwrappedReadPref() && !request.getRequestReshardingResumeToken().has_value() && + !request.getExchange(); + + ABTUnsupportedDocumentSourceVisitor visitor; + DocumentSourceWalker walker(nullptr /*preVisitor*/, &visitor); + walker.walk(pipeline); + bool eligiblePipeline = visitor.eligible; + + return commandOptionsEligible && eligiblePipeline; +} + +bool isEligibleForBonsai(const FindCommandRequest& request, + const MatchExpression& expression, + OperationContext* opCtx, + const CollectionPtr& collection) { + if (auto forceBonsai = shouldForceBonsai(); forceBonsai.has_value()) { + return *forceBonsai; + } + + bool commandOptionsEligible = isEligibleCommon(request, opCtx, collection); + + bool eligibleMatch = true; + ABTMatchExpressionVisitor visitor(eligibleMatch); + MatchExpressionWalker walker(nullptr /*preVisitor*/, nullptr /*inVisitor*/, &visitor); + tree_walker::walk<true, MatchExpression>(&expression, &walker); + + return commandOptionsEligible && eligibleMatch; +} + +} // namespace mongo diff --git a/src/mongo/db/commands/cqf/cqf_command_utils.h b/src/mongo/db/commands/cqf/cqf_command_utils.h new file mode 100644 index 00000000000..a88b0b712d6 --- /dev/null +++ b/src/mongo/db/commands/cqf/cqf_command_utils.h @@ -0,0 +1,53 @@ +/** + * 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" + +namespace mongo { + +/** + * Returns whether the given Pipeline and aggregate command is eligible to use the bonsai + * optimizer. + */ +bool isEligibleForBonsai(const AggregateCommandRequest& request, + const Pipeline& pipeline, + OperationContext* opCtx, + const CollectionPtr& collection); + +/** + * Returns whether the given find command is eligible to use the bonsai optimizer. + */ +bool isEligibleForBonsai(const FindCommandRequest& request, + const MatchExpression& expression, + OperationContext* opCtx, + const CollectionPtr& collection); + +} // namespace mongo diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 756e0e265b0..eda3c32b291 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -36,6 +36,7 @@ #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" @@ -301,9 +302,8 @@ public: // 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() || - (feature_flags::gfeatureFlagCommonQueryFramework.isEnabled( - serverGlobalParams.featureCompatibility) && - internalQueryEnableCascadesOptimizer.load())) { + isEligibleForBonsai( + cq->getFindCommandRequest(), *cq->root(), opCtx, ctx->getCollection())) { // Relinquish locks. The aggregation command will re-acquire them. ctx.reset(); @@ -507,9 +507,8 @@ public: // 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() || - (feature_flags::gfeatureFlagCommonQueryFramework.isEnabled( - serverGlobalParams.featureCompatibility) && - internalQueryEnableCascadesOptimizer.load())) { + isEligibleForBonsai( + cq->getFindCommandRequest(), *cq->root(), opCtx, ctx->getCollection())) { // 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 252e1823c99..e62aa58a35d 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -43,6 +43,7 @@ #include "mongo/db/catalog/database_holder.h" #include "mongo/db/change_stream_change_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" @@ -953,9 +954,7 @@ Status runAggregate(OperationContext* opCtx, constexpr bool alreadyOptimized = true; pipeline->validateCommon(alreadyOptimized); - if (feature_flags::gfeatureFlagCommonQueryFramework.isEnabled( - serverGlobalParams.featureCompatibility) && - internalQueryEnableCascadesOptimizer.load()) { + if (isEligibleForBonsai(request, *pipeline, opCtx, collections.getMainCollection())) { uassert(6624344, "Exchanging is not supported in the Cascades optimizer", !request.getExchange().has_value()); diff --git a/src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp b/src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp index e2d31973a20..863769c9f5b 100644 --- a/src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp +++ b/src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp @@ -49,6 +49,7 @@ #include "mongo/db/pipeline/document_source_internal_inhibit_optimization.h" #include "mongo/db/pipeline/document_source_internal_shard_filter.h" #include "mongo/db/pipeline/document_source_internal_split_pipeline.h" +#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h" #include "mongo/db/pipeline/document_source_limit.h" #include "mongo/db/pipeline/document_source_list_cached_and_active_users.h" #include "mongo/db/pipeline/document_source_list_local_sessions.h" @@ -178,11 +179,19 @@ private: << static_cast<int>(transformer->getType()) << ")"); } + void assertSupportedPath(const std::string& path) { + uassert(ErrorCodes::InternalErrorNotSupported, + "Projection contains unsupported numeric path component", + !FieldRef(path).hasNumericPathComponents()); + } + void processProjectedPaths(const projection_executor::InclusionNode& node) { std::set<std::string> preservedPaths; node.reportProjectedPaths(&preservedPaths); for (const std::string& preservedPathStr : preservedPaths) { + assertSupportedPath(preservedPathStr); + _builder.integrateFieldPath(FieldPath(preservedPathStr), [](const bool isLastElement, FieldMapEntry& entry) { entry._hasLeadingObj = true; @@ -232,6 +241,8 @@ private: // Handle general expression projection. for (const std::string& computedPathStr : computedPaths) { + assertSupportedPath(computedPathStr); + const FieldPath computedPath(computedPathStr); auto entry = _ctx.getNode(); @@ -272,6 +283,7 @@ private: node.reportProjectedPaths(&preservedPaths); for (const std::string& preservedPathStr : preservedPaths) { + assertSupportedPath(preservedPathStr); _builder.integrateFieldPath(FieldPath(preservedPathStr), [](const bool isLastElement, FieldMapEntry& entry) { if (isLastElement) { @@ -326,6 +338,10 @@ public: unsupportedStage(source); } + void visit(const DocumentSourceInternalUnpackBucket* source) override { + unsupportedStage(source); + } + void visit(const DocumentSourceGroup* source) override { const StringMap<boost::intrusive_ptr<Expression>>& idFields = source->getIdFields(); uassert(6624201, "Empty idFields map", !idFields.empty()); diff --git a/src/mongo/db/pipeline/abt/match_expression_visitor.cpp b/src/mongo/db/pipeline/abt/match_expression_visitor.cpp index 5eef023db6b..bc0416f658c 100644 --- a/src/mongo/db/pipeline/abt/match_expression_visitor.cpp +++ b/src/mongo/db/pipeline/abt/match_expression_visitor.cpp @@ -155,6 +155,8 @@ public: "$in with regexes is not supported.", expr->getRegexes().empty()); + assertSupportedPathExpression(expr); + const auto& equalities = expr->getEqualities(); // $in with an empty equalities list matches nothing; replace with constant false. @@ -406,6 +408,8 @@ private: template <bool isValueElemMatch> void generateElemMatch(const ArrayMatchingMatchExpression* expr) { + assertSupportedPathExpression(expr); + // Returns true if at least one sub-objects matches the condition. const size_t childCount = expr->numChildren(); @@ -484,7 +488,15 @@ private: }); } + void assertSupportedPathExpression(const PathMatchExpression* expr) { + uassert(ErrorCodes::InternalErrorNotSupported, + "Expression contains a numeric path component", + !FieldRef(expr->path()).hasNumericPathComponents()); + } + void generateSimpleComparison(const ComparisonMatchExpressionBase* expr, const Operations op) { + assertSupportedPathExpression(expr); + auto [tag, val] = convertFrom(Value(expr->getData())); const bool isArray = tag == sbe::value::TypeTags::Array; ABT result = make<PathCompare>(op, make<Constant>(tag, val)); diff --git a/src/mongo/db/pipeline/visitors/document_source_visitor.h b/src/mongo/db/pipeline/visitors/document_source_visitor.h index a0158147e38..1db827e9dfb 100644 --- a/src/mongo/db/pipeline/visitors/document_source_visitor.h +++ b/src/mongo/db/pipeline/visitors/document_source_visitor.h @@ -47,6 +47,7 @@ class DocumentSourceIndexStats; class DocumentSourceInternalInhibitOptimization; class DocumentSourceInternalShardFilter; class DocumentSourceInternalSplitPipeline; +class DocumentSourceInternalUnpackBucket; class DocumentSourceLimit; class DocumentSourceListCachedAndActiveUsers; class DocumentSourceListLocalSessions; @@ -98,6 +99,8 @@ public: tree_walker::MaybeConstPtr<IsConst, DocumentSourceInternalShardFilter> source) = 0; virtual void visit( tree_walker::MaybeConstPtr<IsConst, DocumentSourceInternalSplitPipeline> source) = 0; + virtual void visit( + tree_walker::MaybeConstPtr<IsConst, DocumentSourceInternalUnpackBucket> source) = 0; virtual void visit(tree_walker::MaybeConstPtr<IsConst, DocumentSourceLimit> source) = 0; virtual void visit( tree_walker::MaybeConstPtr<IsConst, DocumentSourceListCachedAndActiveUsers> source) = 0; diff --git a/src/mongo/db/pipeline/visitors/document_source_walker.cpp b/src/mongo/db/pipeline/visitors/document_source_walker.cpp index b0ea004cae9..0fb3dba9967 100644 --- a/src/mongo/db/pipeline/visitors/document_source_walker.cpp +++ b/src/mongo/db/pipeline/visitors/document_source_walker.cpp @@ -44,6 +44,7 @@ #include "mongo/db/pipeline/document_source_internal_inhibit_optimization.h" #include "mongo/db/pipeline/document_source_internal_shard_filter.h" #include "mongo/db/pipeline/document_source_internal_split_pipeline.h" +#include "mongo/db/pipeline/document_source_internal_unpack_bucket.h" #include "mongo/db/pipeline/document_source_limit.h" #include "mongo/db/pipeline/document_source_list_cached_and_active_users.h" #include "mongo/db/pipeline/document_source_list_local_sessions.h" @@ -108,6 +109,7 @@ void DocumentSourceWalker::walk(const Pipeline& pipeline) { visitHelper<DocumentSourceInternalInhibitOptimization>(ds) || visitHelper<DocumentSourceInternalShardFilter>(ds) || visitHelper<DocumentSourceInternalSplitPipeline>(ds) || + visitHelper<DocumentSourceInternalUnpackBucket>(ds) || visitHelper<DocumentSourceLimit>(ds) || visitHelper<DocumentSourceListCachedAndActiveUsers>(ds) || visitHelper<DocumentSourceListLocalSessions>(ds) || diff --git a/src/mongo/db/query/query_feature_flags.idl b/src/mongo/db/query/query_feature_flags.idl index 76a436b0609..b3339d4a4f1 100644 --- a/src/mongo/db/query/query_feature_flags.idl +++ b/src/mongo/db/query/query_feature_flags.idl @@ -115,7 +115,7 @@ feature_flags: featureFlagCommonQueryFramework: description: "Feature flag for allowing use of Cascades-based query optimizer" - cpp_varname: gfeatureFlagCommonQueryFramework + cpp_varname: gFeatureFlagCommonQueryFramework default: false featureFlagLastPointQuery: diff --git a/src/mongo/db/query/query_knobs.idl b/src/mongo/db/query/query_knobs.idl index f894629037f..53c1e5e7617 100644 --- a/src/mongo/db/query/query_knobs.idl +++ b/src/mongo/db/query/query_knobs.idl @@ -728,7 +728,7 @@ server_parameters: set_at: [ startup, runtime ] cpp_varname: "internalQueryEnableCascadesOptimizer" cpp_vartype: AtomicWord<bool> - default: false + default: true internalCascadesOptimizerDisableScan: description: "Disable full collection scans in the Cascades optimizer." @@ -780,6 +780,14 @@ server_parameters: cpp_vartype: AtomicWord<bool> default: false + internalQueryForceCommonQueryFramework: + description: "Set to always use the bonsai optimizer, regardless of the query." + set_at: [ startup, runtime ] + cpp_varname: "internalQueryForceCommonQueryFramework" + cpp_vartype: AtomicWord<bool> + test_only: true + default: false + internalQueryCollectionMaxNoOfDocumentsToChooseHashJoin: description: "Up to what number of documents do we choose the hash join algorithm when $lookup is translated to a SBE plan." |