summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/cqf.yml2
-rw-r--r--buildscripts/resmokeconfig/suites/cqf_parallel.yml2
-rw-r--r--jstests/cqf/match_with_in.js6
-rw-r--r--jstests/libs/optimizer_utils.js22
-rw-r--r--jstests/noPassthrough/cqf_fallback.js204
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/cqf/cqf_aggregate.cpp23
-rw-r--r--src/mongo/db/commands/cqf/cqf_command_utils.cpp696
-rw-r--r--src/mongo/db/commands/cqf/cqf_command_utils.h53
-rw-r--r--src/mongo/db/commands/find_cmd.cpp11
-rw-r--r--src/mongo/db/commands/run_aggregate.cpp5
-rw-r--r--src/mongo/db/pipeline/abt/abt_document_source_visitor.cpp16
-rw-r--r--src/mongo/db/pipeline/abt/match_expression_visitor.cpp12
-rw-r--r--src/mongo/db/pipeline/visitors/document_source_visitor.h3
-rw-r--r--src/mongo/db/pipeline/visitors/document_source_walker.cpp2
-rw-r--r--src/mongo/db/query/query_feature_flags.idl2
-rw-r--r--src/mongo/db/query/query_knobs.idl10
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."