summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlya Berciu <alyacarina@gmail.com>2021-03-30 16:29:12 +0100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-04-21 10:33:05 +0000
commit8736544c4a2f4fbd38792f656408f7b807b786d7 (patch)
tree954c7234c94454e41a1714b41718e241ded9e353
parentc90de3e65fff76ead7b8ca4664c3f34b0c913e04 (diff)
downloadmongo-8736544c4a2f4fbd38792f656408f7b807b786d7.tar.gz
SERVER-30417: Implement $getField expression
Co-authored-by: Katherine Wu <katherine.wu@mongodb.com>
-rw-r--r--jstests/aggregation/expressions/expression_get_field.js220
-rw-r--r--src/mongo/db/pipeline/expression.cpp80
-rw-r--r--src/mongo/db/pipeline/expression.h62
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp25
-rw-r--r--src/mongo/db/pipeline/expression_visitor.h2
-rw-r--r--src/mongo/db/query/sbe_stage_builder_expression.cpp6
6 files changed, 395 insertions, 0 deletions
diff --git a/jstests/aggregation/expressions/expression_get_field.js b/jstests/aggregation/expressions/expression_get_field.js
new file mode 100644
index 00000000000..991a2cd7766
--- /dev/null
+++ b/jstests/aggregation/expressions/expression_get_field.js
@@ -0,0 +1,220 @@
+/**
+ * Tests basic functionality of the $getField expression.
+ */
+(function() {
+"use strict";
+
+load("jstests/aggregation/extras/utils.js"); // For assertArrayEq.
+
+const coll = db.expression_get_field;
+coll.drop();
+
+for (let i = 0; i < 2; i++) {
+ assert.commandWorked(coll.insert({
+ _id: i,
+ x: i,
+ y: "c",
+ "a$b": "foo",
+ "a.b": "bar",
+ "a.$b": 5,
+ ".xy": i,
+ ".$xz": i,
+ "..zz": i,
+ c: {d: "x"},
+ }));
+}
+
+// Test that $getField fails with the provided 'code' for invalid arguments 'getFieldArgs'.
+function assertGetFieldFailedWithCode(getFieldArgs, code) {
+ const error =
+ assert.throws(() => coll.aggregate([{$project: {test: {$getField: getFieldArgs}}}]));
+ assert.commandFailedWithCode(error, code);
+}
+
+// Test that $getField returns the 'expected' results for the given arguments 'getFieldArgs'.
+function assertGetFieldResultsEq(getFieldArgs, expected) {
+ assertPipelineResultsEq([{$project: {_id: 1, test: {$getField: getFieldArgs}}}], expected);
+}
+
+// Test the given 'pipeline' returns the 'expected' results.
+function assertPipelineResultsEq(pipeline, expected) {
+ const actual = coll.aggregate(pipeline).toArray();
+ assertArrayEq({actual, expected});
+}
+
+const isDotsAndDollarsEnabled = db.adminCommand({getParameter: 1, featureFlagDotsAndDollars: 1})
+ .featureFlagDotsAndDollars.value;
+
+if (!isDotsAndDollarsEnabled) {
+ // Verify that $getField is not available if the feature flag is set to false and don't
+ // run the rest of the test.
+ assertGetFieldFailedWithCode({field: "a", from: {a: "b"}}, 31325);
+ return;
+}
+
+// Test that $getField fails with a document missing named arguments.
+assertGetFieldFailedWithCode({from: {a: "b"}}, 3041702);
+assertGetFieldFailedWithCode({field: "a"}, 3041703);
+
+// Test that $getField fails with a document with one or more arguments of incorrect type.
+assertGetFieldFailedWithCode({field: true, from: {a: "b"}}, 3041704);
+assertGetFieldFailedWithCode({field: {"a": 1}, from: {"a": 1}}, 3041704);
+assertGetFieldFailedWithCode({field: "a", from: true}, 3041705);
+assertGetFieldFailedWithCode(5, 3041704);
+assertGetFieldFailedWithCode(true, 3041704);
+assertGetFieldFailedWithCode({$add: [2, 3]}, 3041704);
+
+// Test that $getField fails with a document with invalid arguments.
+assertGetFieldFailedWithCode({field: "a", from: {a: "b"}, unknown: true}, 3041701);
+
+// Test that $getField returns the correct value from the provided object.
+assertGetFieldResultsEq({field: "a", from: {a: "b"}}, [{_id: 0, test: "b"}, {_id: 1, test: "b"}]);
+
+// Test that $getField returns the correct value from the $$ROOT object.
+assertGetFieldResultsEq(null, [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq("a", [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq("a$b", [{_id: 0, test: "foo"}, {_id: 1, test: "foo"}]);
+assertGetFieldResultsEq("a.b", [{_id: 0, test: "bar"}, {_id: 1, test: "bar"}]);
+assertGetFieldResultsEq("x", [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+assertGetFieldResultsEq("a.$b", [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq(".xy", [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+assertGetFieldResultsEq(".$xz", [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+assertGetFieldResultsEq("..zz", [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+
+// Test that $getField returns the correct value from the $$ROOT object when field is an expression.
+assertGetFieldResultsEq({$concat: ["a", "b"]},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq({$concat: ["a", {$const: "$"}, "b"]},
+ [{_id: 0, test: "foo"}, {_id: 1, test: "foo"}]);
+assertGetFieldResultsEq({$cond: [true, null, "x"]}, [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq({$cond: [false, null, "x"]}, [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+
+// Test that $getField treats dotted fields as key literals instead of field paths. Note that it is
+// necessary to use $const in places, otherwise object field validation would reject some of these
+// field names.
+assertGetFieldResultsEq({field: "a.b", from: {$const: {"a.b": "b"}}},
+ [{_id: 0, test: "b"}, {_id: 1, test: "b"}]);
+assertGetFieldResultsEq({field: ".ab", from: {$const: {".ab": "b"}}},
+ [{_id: 0, test: "b"}, {_id: 1, test: "b"}]);
+assertGetFieldResultsEq({field: "ab.", from: {$const: {"ab.": "b"}}},
+ [{_id: 0, test: "b"}, {_id: 1, test: "b"}]);
+assertGetFieldResultsEq({field: "a.b.c", from: {$const: {"a.b.c": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: "a.b.c", from: {a: {b: {c: 5}}}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq({field: {$concat: ["a.b", ".", "c"]}, from: {$const: {"a.b.c": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$concat: ["a.b", ".", "c"]}, from: {a: {b: {c: 5}}}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+
+// Test that $getField works with fields that contain '$'.
+assertGetFieldResultsEq({field: "a$b", from: {"a$b": "b"}},
+ [{_id: 0, test: "b"}, {_id: 1, test: "b"}]);
+assertGetFieldResultsEq({field: "a$b.b", from: {$const: {"a$b.b": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$const: "a$b.b"}, from: {$const: {"a$b.b": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$const: "$b.b"}, from: {$const: {"$b.b": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$const: "$b"}, from: {$const: {"$b": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$const: "$.ab"}, from: {$const: {"$.ab": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: {$const: "$$xz"}, from: {$const: {"$$xz": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+
+// Test null and missing cases.
+assertGetFieldResultsEq({field: "a", from: null}, [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq({field: null, from: {a: 1}}, [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq({field: "a", from: {b: 2, c: 3}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq({field: "a", from: {a: null, b: 2, c: 3}},
+ [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq({field: {$const: "$a"}, from: {$const: {"$a": null, b: 2, c: 3}}},
+ [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq({field: "a", from: {}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+
+// These should return null because "$a.b" evaluates to a field path expression which returns a
+// nullish value (so the expression should return null), as there is no $a.b field path.
+assertGetFieldResultsEq({field: "$a.b", from: {$const: {"$a.b": 5}}},
+ [{_id: 0, test: null}, {_id: 1, test: null}]);
+assertGetFieldResultsEq("$a.b", [{_id: 0, test: null}, {_id: 1, test: null}]);
+
+// When the field path does actually resolve to a field, the value of that field should be used.
+
+// The fieldpath $y resolves to "c" in $$ROOT.
+assertGetFieldResultsEq({field: "$y", from: {$const: {"c": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: "$y", from: {$const: {"a": 5}}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq("$y", [{_id: 0, test: {d: "x"}}, {_id: 1, test: {d: "x"}}]);
+
+// The fieldpath $c.d resolves to "x" in $$ROOT.
+assertGetFieldResultsEq({field: "$c.d", from: {$const: {"x": 5}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq({field: "$c.d", from: {$const: {"y": 5}}},
+ [{_id: 0}, {_id: 1}]); // The test field should evaluate to missing.
+assertGetFieldResultsEq("$c.d", [{_id: 0, test: 0}, {_id: 1, test: 1}]);
+
+// $x resolves to a number, so this should fail.
+assertGetFieldFailedWithCode({field: "$x", from: {$const: {"c": 5}}}, 3041704);
+assertGetFieldFailedWithCode("$x", 3041704);
+
+// Test case where $getField stages are nested.
+assertGetFieldResultsEq(
+ {field: "a", from: {$getField: {field: "b.c", from: {$const: {"b.c": {a: 5}}}}}},
+ [{_id: 0, test: 5}, {_id: 1, test: 5}]);
+assertGetFieldResultsEq(
+ {field: "x", from: {$getField: {field: "b.c", from: {$const: {"b.c": {a: 5}}}}}},
+ [{_id: 0}, {_id: 1}]);
+assertGetFieldResultsEq(
+ {field: "a", from: {$getField: {field: "b.d", from: {$const: {"b.c": {a: 5}}}}}},
+ [{_id: 0, test: null}, {_id: 1, test: null}]);
+
+// Test case when a dotted/dollar path is within an array.
+assertGetFieldResultsEq({
+ field: {$const: "a$b"},
+ from: {$arrayElemAt: [[{$const: {"a$b": 1}}, {$const: {"a$b": 2}}], 0]}
+},
+ [{_id: 0, test: 1}, {_id: 1, test: 1}]);
+assertGetFieldResultsEq({
+ field: {$const: "a.."},
+ from: {$arrayElemAt: [[{$const: {"a..": 1}}, {$const: {"a..": 2}}], 1]}
+},
+ [{_id: 0, test: 2}, {_id: 1, test: 2}]);
+
+// Test $getField expression with other pipeline stages.
+
+assertPipelineResultsEq(
+ [
+ {$match: {$expr: {$eq: [{$getField: "_id"}, {$getField: ".$xz"}]}}},
+ {$project: {aa: {$getField: ".$xz"}, "_id": 1}},
+ ],
+ [{_id: 0, aa: 0}, {_id: 1, aa: 1}]);
+
+assertPipelineResultsEq([{$match: {$expr: {$ne: [{$getField: "_id"}, {$getField: ".$xz"}]}}}], []);
+assertPipelineResultsEq(
+ [
+ {$match: {$expr: {$ne: [{$getField: "_id"}, {$getField: "a.b"}]}}},
+ {$project: {"a": {$getField: "x"}, "b": {$getField: {$const: "a.b"}}}}
+ ],
+ [{_id: 0, a: 0, b: "bar"}, {_id: 1, a: 1, b: "bar"}]);
+
+assertPipelineResultsEq(
+ [
+ {$addFields: {aa: {$getField: {$const: "a.b"}}}},
+ {$project: {aa: 1, _id: 1}},
+ ],
+ [{_id: 0, aa: "bar"}, {_id: 1, aa: "bar"}]);
+
+assertPipelineResultsEq(
+ [
+ {$bucket: {groupBy: {$getField: {$const: "a.b"}}, boundaries: ["aaa", "bar", "zzz"]}}
+ ], // We should get one bucket here ("bar") with two documents.
+ [{_id: "bar", count: 2}]);
+assertPipelineResultsEq([{
+ $bucket: {groupBy: {$getField: "x"}, boundaries: [0, 1, 2, 3, 4]}
+ }], // We should get two buckets here for the two possible values of x.
+ [{_id: 0, count: 1}, {_id: 1, count: 1}]);
+})(); \ No newline at end of file
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 894ef5893aa..4fc2b25e7ef 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -7187,6 +7187,86 @@ void ExpressionDateTrunc::_doAddDependencies(DepsTracker* deps) const {
}
}
+/* -------------------------- ExpressionGetField ------------------------------ */
+REGISTER_FEATURE_FLAG_GUARDED_EXPRESSION(getField,
+ ExpressionGetField::parse,
+ feature_flags::gFeatureFlagDotsAndDollars);
+
+intrusive_ptr<Expression> ExpressionGetField::parse(ExpressionContext* const expCtx,
+ BSONElement expr,
+ const VariablesParseState& vps) {
+ boost::intrusive_ptr<Expression> fieldExpr;
+ boost::intrusive_ptr<Expression> fromExpr;
+
+ if (expr.type() == BSONType::Object) {
+ for (auto&& elem : expr.embeddedObject()) {
+ const auto fieldName = elem.fieldNameStringData();
+ if (!fieldExpr && !fromExpr && fieldName[0] == '$') {
+ // This may be an expression, so we should treat it as such.
+ fieldExpr = Expression::parseOperand(expCtx, expr, vps);
+ fromExpr = ExpressionFieldPath::parse(expCtx, "$$ROOT", vps);
+ break;
+ } else if (fieldName == "field"_sd) {
+ fieldExpr = Expression::parseOperand(expCtx, elem, vps);
+ } else if (fieldName == "from"_sd) {
+ fromExpr = Expression::parseOperand(expCtx, elem, vps);
+ } else {
+ uasserted(3041701,
+ str::stream()
+ << kExpressionName << " found an unknown argument: " << fieldName);
+ }
+ }
+ } else {
+ fieldExpr = Expression::parseOperand(expCtx, expr, vps);
+ fromExpr = ExpressionFieldPath::parse(expCtx, "$$ROOT", vps);
+ }
+
+ uassert(3041702,
+ str::stream() << kExpressionName << " requires 'field' to be specified",
+ fieldExpr);
+ uassert(
+ 3041703, str::stream() << kExpressionName << " requires 'from' to be specified", fromExpr);
+
+ return make_intrusive<ExpressionGetField>(expCtx, fieldExpr, fromExpr);
+}
+
+Value ExpressionGetField::evaluate(const Document& root, Variables* variables) const {
+ auto fieldValue = _field->evaluate(root, variables);
+ if (fieldValue.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ auto fromValue = _from->evaluate(root, variables);
+ if (fromValue.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ uassert(3041704,
+ str::stream() << kExpressionName << " requires 'field' to evaluate to type String",
+ fieldValue.getType() == BSONType::String);
+
+ uassert(3041705,
+ str::stream() << kExpressionName << " requires 'from' to evaluate to type Object",
+ fromValue.getType() == BSONType::Object);
+
+ return fromValue.getDocument().getField(fieldValue.getString());
+}
+
+intrusive_ptr<Expression> ExpressionGetField::optimize() {
+ return intrusive_ptr<Expression>(this);
+}
+
+void ExpressionGetField::_doAddDependencies(DepsTracker* deps) const {
+ _from->addDependencies(deps);
+ _field->addDependencies(deps);
+}
+
+Value ExpressionGetField::serialize(const bool explain) const {
+ return Value(Document{{"$getField"_sd,
+ Document{{"field"_sd, _field->serialize(explain)},
+ {"from"_sd, _from->serialize(explain)}}}});
+}
+
MONGO_INITIALIZER(expressionParserMap)(InitializerContext*) {
// Nothing to do. This initializer exists to tie together all the individual initializers
// defined by REGISTER_EXPRESSION / REGISTER_EXPRESSION_WITH_MIN_VERSION.
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index f89e45a46a1..e0571735a18 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -50,6 +50,7 @@
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/variables.h"
#include "mongo/db/query/datetime/date_time_support.h"
+#include "mongo/db/query/query_feature_flags_gen.h"
#include "mongo/db/query/sort_pattern.h"
#include "mongo/db/server_options.h"
#include "mongo/util/intrusive_counter.h"
@@ -78,6 +79,25 @@ class DocumentSource;
}
/**
+ * Registers a Parser so it can be called from parseExpression and friends (but only if
+ * 'featureFlag' is enabled).
+ *
+ * As an example, if your expression looks like {"$foo": [1,2,3]} and should be flag-guarded by
+ * feature_flags::gFoo, you would add this line:
+ * REGISTER_FEATURE_FLAG_GUARDED_EXPRESSION(foo, ExpressionFoo::parse, feature_flags::gFoo);
+ *
+ * An expression registered this way can be used in any featureCompatibilityVersion.
+ */
+#define REGISTER_FEATURE_FLAG_GUARDED_EXPRESSION(key, parser, featureFlag) \
+ MONGO_INITIALIZER_GENERAL( \
+ addToExpressionParserMap_##key, ("default"), ("expressionParserMap")) \
+ (InitializerContext*) { \
+ if (featureFlag.isEnabledAndIgnoreFCV()) { \
+ Expression::registerExpression("$" #key, (parser), boost::none); \
+ } \
+ }
+
+/**
* Registers a Parser so it can be called from parseExpression and friends. Use this version if your
* expression can only be persisted to a catalog data structure in a feature compatibility version
* >= X.
@@ -3472,4 +3492,46 @@ private:
// Accepted BSON type: String. If not specified, "sunday" is used.
boost::intrusive_ptr<Expression>& _startOfWeek;
};
+
+class ExpressionGetField final : public Expression {
+public:
+ static boost::intrusive_ptr<Expression> parse(ExpressionContext* const expCtx,
+ BSONElement exprElement,
+ const VariablesParseState& vps);
+
+ /**
+ * Constructs a $getField expression where 'field' is an expression resolving to a string Value
+ * (or null) and 'from' is an expression resolving to an object Value (or null).
+ *
+ * If either 'field' or 'from' is nullish, $getField evaluates to null. Furthermore, if 'from'
+ * does not contain 'field', then $getField returns missing.
+ */
+ ExpressionGetField(ExpressionContext* const expCtx,
+ boost::intrusive_ptr<Expression> field,
+ boost::intrusive_ptr<Expression> from)
+ : Expression(expCtx, {std::move(field), std::move(from)}),
+ _field(_children[0]),
+ _from(_children[1]) {
+ expCtx->sbeCompatible = false;
+ }
+
+ Value serialize(const bool explain) const final;
+
+ Value evaluate(const Document& root, Variables* variables) const final;
+
+ boost::intrusive_ptr<Expression> optimize() final;
+
+ void acceptVisitor(ExpressionVisitor* visitor) final {
+ return visitor->visit(this);
+ }
+
+ static constexpr auto kExpressionName = "$getField"_sd;
+
+protected:
+ void _doAddDependencies(DepsTracker* deps) const final override;
+
+private:
+ boost::intrusive_ptr<Expression>& _field;
+ boost::intrusive_ptr<Expression>& _from;
+};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp
index 41bb7c2df80..0d1d9dd041f 100644
--- a/src/mongo/db/pipeline/expression_test.cpp
+++ b/src/mongo/db/pipeline/expression_test.cpp
@@ -3127,4 +3127,29 @@ TEST(ExpressionSubtractTest, OverflowLong) {
ASSERT_EQ(result.getDouble(), static_cast<double>(minLong) * -1);
}
+TEST(ExpressionGetFieldTest, GetFieldSerializesStringArgumentCorrectly) {
+ auto expCtx = ExpressionContextForTest{};
+ VariablesParseState vps = expCtx.variablesParseState;
+ BSONObj expr = fromjson("{$meta: \"foo\"}");
+ auto expression = ExpressionGetField::parse(&expCtx, expr.firstElement(), vps);
+ ASSERT_BSONOBJ_EQ(BSON("ignoredField" << BSON("$getField" << BSON("field" << BSON("$const"
+ << "foo")
+ << "from"
+ << "$$ROOT"))),
+ BSON("ignoredField" << expression->serialize(false)));
+}
+
+TEST(ExpressionGetFieldTest, GetFieldSerializesCorrectly) {
+ auto expCtx = ExpressionContextForTest{};
+ VariablesParseState vps = expCtx.variablesParseState;
+ BSONObj expr = fromjson("{$meta: {\"field\": \"foo\", \"from\": {a: 1}}}");
+ auto expression = ExpressionGetField::parse(&expCtx, expr.firstElement(), vps);
+ ASSERT_BSONOBJ_EQ(BSON("ignoredField"
+ << BSON("$getField"
+ << BSON("field" << BSON("$const"
+ << "foo")
+ << "from" << BSON("a" << BSON("$const" << 1))))),
+ BSON("ignoredField" << expression->serialize(false)));
+}
+
} // namespace ExpressionTests
diff --git a/src/mongo/db/pipeline/expression_visitor.h b/src/mongo/db/pipeline/expression_visitor.h
index 819e51a03d5..b32ce2ce55d 100644
--- a/src/mongo/db/pipeline/expression_visitor.h
+++ b/src/mongo/db/pipeline/expression_visitor.h
@@ -157,6 +157,7 @@ class ExpressionDateDiff;
class ExpressionDateAdd;
class ExpressionDateSubtract;
class ExpressionDateTrunc;
+class ExpressionGetField;
class AccumulatorAvg;
class AccumulatorMax;
@@ -311,6 +312,7 @@ public:
virtual void visit(ExpressionToHashedIndexKey*) = 0;
virtual void visit(ExpressionDateAdd*) = 0;
virtual void visit(ExpressionDateSubtract*) = 0;
+ virtual void visit(ExpressionGetField*) = 0;
};
} // namespace mongo
diff --git a/src/mongo/db/query/sbe_stage_builder_expression.cpp b/src/mongo/db/query/sbe_stage_builder_expression.cpp
index fff1f93de32..4d6af930619 100644
--- a/src/mongo/db/query/sbe_stage_builder_expression.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_expression.cpp
@@ -485,6 +485,7 @@ public:
void visit(ExpressionToHashedIndexKey* expr) final {}
void visit(ExpressionDateAdd* expr) final {}
void visit(ExpressionDateSubtract* expr) final {}
+ void visit(ExpressionGetField* expr) final {}
private:
void visitMultiBranchLogicExpression(Expression* expr, sbe::EPrimBinary::Op logicOp) {
@@ -684,6 +685,7 @@ public:
void visit(ExpressionToHashedIndexKey* expr) final {}
void visit(ExpressionDateAdd* expr) final {}
void visit(ExpressionDateSubtract* expr) final {}
+ void visit(ExpressionGetField* expr) final {}
private:
void visitMultiBranchLogicExpression(Expression* expr, sbe::EPrimBinary::Op logicOp) {
@@ -2647,6 +2649,10 @@ public:
generateDateArithmeticsExpression(expr, "dateSubtract");
}
+ void visit(ExpressionGetField* expr) final {
+ unsupportedExpression("$getField");
+ }
+
private:
/**
* Shared logic for $and, $or. Converts each child into an EExpression that evaluates to Boolean