diff options
author | Arun Banala <arun.banala@mongodb.com> | 2019-06-12 11:25:59 +0100 |
---|---|---|
committer | Arun Banala <arun.banala@mongodb.com> | 2019-06-13 19:17:14 +0100 |
commit | 1e02fdb576d8167562587cd18c5c7132e9675639 (patch) | |
tree | 33cd8a039e66203c6e139e42fe77717d8dc7a1bd | |
parent | 6329d3f5cbfcea79ec9d49a7e0123890b7fd040e (diff) | |
download | mongo-1e02fdb576d8167562587cd18c5c7132e9675639.tar.gz |
SERVER-41065 Make agg evaluate() thread safe by passing 'Variables' as a parameter
(cherry picked from commit ab9d3aaad1cb9ad42063c1291ea07e321260a3d1)
20 files changed, 738 insertions, 499 deletions
diff --git a/buildscripts/resmokeconfig/suites/aggregation_sharded_collections_passthrough.yml b/buildscripts/resmokeconfig/suites/aggregation_sharded_collections_passthrough.yml index 85b7a90db67..3a0969386a4 100644 --- a/buildscripts/resmokeconfig/suites/aggregation_sharded_collections_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/aggregation_sharded_collections_passthrough.yml @@ -34,6 +34,9 @@ selector: # Uses the profiler, which is not supported through mongos. - jstests/aggregation/sources/lookup/profile_lookup.js + # TODO SERVER-32309: Enable once $lookup with pipeline supports sharded foreign collections. + - jstests/aggregation/sources/graphLookup/variables.js + exclude_with_any_tags: # Tests tagged with the following will fail because they assume collections are not sharded. - assumes_no_implicit_collection_creation_after_drop diff --git a/jstests/aggregation/bugs/server9840.js b/jstests/aggregation/bugs/server9840.js deleted file mode 100644 index 4316730370a..00000000000 --- a/jstests/aggregation/bugs/server9840.js +++ /dev/null @@ -1,99 +0,0 @@ -// SERVER-9840 variables in expressions and $let - -load('jstests/aggregation/extras/utils.js'); -var t = db.server9840; -t.drop(); - -function test(expression, expected) { - t.drop(); - t.insert({zero: 0, one: 1, two: 2, three: 3, nested: {four: 4}}); - - // Test in projection: - var result = t.aggregate({$project: {_id: 0, res: expression}}).toArray(); - assert.eq(result, [{res: expected}]); - - // Test in group: - var result = t.aggregate({$group: {_id: 0, res: {$sum: expression}}}).toArray(); - assert.eq(result, [{_id: 0, res: expected}]); -} - -// basics -test('$two', 2); -test('$$CURRENT.two', 2); -test('$$ROOT.two', 2); - -// using sub expressions -test({$add: ['$two', '$$CURRENT.three']}, 5); -test({$add: ['$$CURRENT.two', '$$ROOT.nested.four']}, 6); - -// $let simple -test({$let: {vars: {a: 10}, in : '$$a'}}, 10); -test({$let: {vars: {a: '$zero'}, in : '$$a'}}, 0); -test({$let: {vars: {a: {$add: ['$one', '$two']}, b: 10}, in : {$multiply: ['$$a', '$$b']}}}, 30); - -// $let changing CURRENT -test({$let: {vars: {CURRENT: '$$ROOT.nested'}, in : {$multiply: ['$four', '$$ROOT.two']}}}, 8); -test({ - $let: { - vars: {CURRENT: '$$CURRENT.nested'}, // using original value of CURRENT - in : {$multiply: ['$four', '$$ROOT.two']} - } -}, - 8); -test({ - $let: { - vars: {CURRENT: '$nested'}, // same as last - in : {$multiply: ['$four', '$$ROOT.two']} - } -}, - 8); -test({ - $let: { - vars: {CURRENT: {$const: {ten: 10}}}, // "artificial" object - in : {$multiply: ['$ten', '$$ROOT.two']} - } -}, - 20); -test({ - $let: { - vars: {CURRENT: '$three'}, // sets current to the number 3 (not an object) - in : {$multiply: ['$$CURRENT', '$$ROOT.two']} - } -}, - 6); - -// swapping with $let (ensures there is no ordering dependency in vars) -test({ - $let: { - vars: {x: 6, y: 10}, - in : { - $let: { - vars: {x: '$$y', y: '$$x'}, // now {x:10, y:6} - in : {$subtract: ['$$x', '$$y']} - } - } - } -}, // not commutative! - 4); // 10-6 not 6-10 or 6-6 - -// unicode is allowed -test({$let: {vars: {'日本語': 10}, in : '$$日本語'}}, 10); // Japanese for "Japanese language" - -// Can use ROOT and CURRENT directly with no subfield (SERVER-5916) -t.drop(); -t.insert({_id: 'obj'}); -assert.eq(t.aggregate({$project: {_id: 0, obj: '$$ROOT'}}).toArray(), [{obj: {_id: 'obj'}}]); -assert.eq(t.aggregate({$project: {_id: 0, obj: '$$CURRENT'}}).toArray(), [{obj: {_id: 'obj'}}]); -assert.eq(t.aggregate({$group: {_id: 0, objs: {$push: '$$ROOT'}}}).toArray(), - [{_id: 0, objs: [{_id: 'obj'}]}]); -assert.eq(t.aggregate({$group: {_id: 0, objs: {$push: '$$CURRENT'}}}).toArray(), - [{_id: 0, objs: [{_id: 'obj'}]}]); - -// check name validity checks -assertErrorCode(t, {$project: {a: {$let: {vars: {ROOT: 1}, in : '$$ROOT'}}}}, 16867); -assertErrorCode(t, {$project: {a: {$let: {vars: {FOO: 1}, in : '$$FOO'}}}}, 16867); -assertErrorCode(t, {$project: {a: {$let: {vars: {_underbar: 1}, in : '$$FOO'}}}}, 16867); -assertErrorCode(t, {$project: {a: {$let: {vars: {'a.b': 1}, in : '$$FOO'}}}}, 16868); -assertErrorCode(t, {$project: {a: {$let: {vars: {'a b': 1}, in : '$$FOO'}}}}, 16868); -assertErrorCode(t, {$project: {a: '$$_underbar'}}, 16870); -assertErrorCode(t, {$project: {a: '$$with spaces'}}, 16871); diff --git a/jstests/aggregation/expressions/let.js b/jstests/aggregation/expressions/let.js new file mode 100644 index 00000000000..b4cfb7d3188 --- /dev/null +++ b/jstests/aggregation/expressions/let.js @@ -0,0 +1,132 @@ +/** + * Basic integration tests for the $let expression. + */ +(function() { + "use strict"; + + load("jstests/aggregation/extras/utils.js"); // For assertErrorCode. + + let coll = db.agg_expr_let; + coll.drop(); + assert.writeOK(coll.insert({zero: 0, one: 1, two: 2, three: 3, nested: {four: 4}})); + + function testExpr(expression, output) { + const res = coll.aggregate([{$project: {output: expression}}]).toArray(); + assert.eq(res.length, 1, tojson(res)); + assert.eq(res[0].output, output, tojson(res)); + + // Test in group: + const result = coll.aggregate({$group: {_id: 0, res: {$sum: expression}}}).toArray(); + assert.eq(result, [{_id: 0, res: output}]); + } + + // Basic tests. + testExpr('$two', 2); + testExpr('$$CURRENT.two', 2); + testExpr('$$ROOT.two', 2); + + // Using sub expressions. + testExpr({$add: ['$two', '$$CURRENT.three']}, 5); + testExpr({$add: ['$$CURRENT.two', '$$ROOT.nested.four']}, 6); + + // Verify that the variables defined in $let work. + testExpr({$let: {vars: {a: 10}, in : '$$a'}}, 10); + testExpr({$let: {vars: {a: '$zero'}, in : '$$a'}}, 0); + testExpr({$let: {vars: {a: {$add: ['$one', '$two']}, b: 10}, in : {$multiply: ['$$a', '$$b']}}}, + 30); + + // Verify that the outer level variable works in inner level $let. + testExpr({ + $let: { + vars: {var1: 1}, + in : {$let: {vars: {var2: "$$var1"}, in : {$sum: ["$$var1", "$$var2"]}}} + } + }, + 2); + + // Verify that the outer level variables get overwritten by inner level variables. + testExpr({ + $let: { + vars: {var1: "$one"}, + in : {$let: {vars: {var2: "$$var1", var1: 3}, in : {$sum: ["$$var2", "$$var1"]}}} + } + }, + 4); + + // $let changing CURRENT + testExpr({$let: {vars: {CURRENT: '$$ROOT.nested'}, in : {$multiply: ['$four', '$$ROOT.two']}}}, + 8); + testExpr({ + $let: { + vars: {CURRENT: '$$CURRENT.nested'}, // using original value of CURRENT + in : {$multiply: ['$four', '$$ROOT.two']} + } + }, + 8); + testExpr({ + $let: { + vars: {CURRENT: '$nested'}, // same as last + in : {$multiply: ['$four', '$$ROOT.two']} + } + }, + 8); + testExpr({ + $let: { + vars: {CURRENT: {$const: {ten: 10}}}, // "artificial" object + in : {$multiply: ['$ten', '$$ROOT.two']} + } + }, + 20); + testExpr({ + $let: { + vars: {CURRENT: '$three'}, // sets current to the number 3 (not an object) + in : {$multiply: ['$$CURRENT', '$$ROOT.two']} + } + }, + 6); + + // Swapping with $let (ensures there is no ordering dependency in vars). + testExpr({ + $let: { + vars: {x: 6, y: 10}, + in : { + $let: { + vars: {x: '$$y', y: '$$x'}, // now {x:10, y:6} + in : {$subtract: ['$$x', '$$y']} + } + } + } + }, // Not commutative! + 4); // 10-6 not 6-10 or 6-6 + + // Unicode is allowed. + testExpr({$let: {vars: {'日本語': 10}, in : '$$日本語'}}, + 10); // Japanese for "Japanese language". + + // Can use ROOT and CURRENT directly with no subfield (SERVER-5916). + coll.drop(); + coll.insert({_id: 'obj'}); + assert.eq(coll.aggregate({$project: {_id: 0, obj: '$$ROOT'}}).toArray(), [{obj: {_id: 'obj'}}]); + assert.eq(coll.aggregate({$project: {_id: 0, obj: '$$CURRENT'}}).toArray(), + [{obj: {_id: 'obj'}}]); + assert.eq(coll.aggregate({$group: {_id: 0, objs: {$push: '$$ROOT'}}}).toArray(), + [{_id: 0, objs: [{_id: 'obj'}]}]); + assert.eq(coll.aggregate({$group: {_id: 0, objs: {$push: '$$CURRENT'}}}).toArray(), + [{_id: 0, objs: [{_id: 'obj'}]}]); + + // Check name validity checks. + assertErrorCode(coll, {$project: {a: {$let: {vars: {ROOT: 1}, in : '$$ROOT'}}}}, 16867); + assertErrorCode(coll, {$project: {a: {$let: {vars: {FOO: 1}, in : '$$FOO'}}}}, 16867); + assertErrorCode(coll, {$project: {a: {$let: {vars: {_underbar: 1}, in : '$$FOO'}}}}, 16867); + assertErrorCode(coll, {$project: {a: {$let: {vars: {'a.b': 1}, in : '$$FOO'}}}}, 16868); + assertErrorCode(coll, {$project: {a: {$let: {vars: {'a b': 1}, in : '$$FOO'}}}}, 16868); + assertErrorCode(coll, {$project: {a: '$$_underbar'}}, 16870); + assertErrorCode(coll, {$project: {a: '$$with spaces'}}, 16871); + + // Verify that variables defined in '$let' cannot be used to initialize other variables. + assertErrorCode( + coll, + [{$project: {output: {$let: {vars: {var1: "$one", var2: "$$var1"}, in : "$$var1"}}}}], + 17276); + +}()); diff --git a/jstests/aggregation/sources/graphLookup/variables.js b/jstests/aggregation/sources/graphLookup/variables.js new file mode 100644 index 00000000000..87e2c8b3975 --- /dev/null +++ b/jstests/aggregation/sources/graphLookup/variables.js @@ -0,0 +1,41 @@ +/** + * Tests to verify that $graphLookup can use the variables defined in an outer scope. + */ +(function() { + "use strict"; + + let local = db.graph_lookup_var_local; + let foreign = db.graph_lookup_var_foreign; + local.drop(); + foreign.drop(); + + foreign.insert({from: "b", to: "a", _id: 0}); + local.insert({}); + + const basicGraphLookup = { + $graphLookup: { + from: "graph_lookup_var_foreign", + startWith: "$$var1", + connectFromField: "from", + connectToField: "to", + as: "resultsFromGraphLookup" + } + }; + + const lookup = { + $lookup: { + from: "graph_lookup_var_local", + let : {var1: "a"}, + pipeline: [basicGraphLookup], + as: "resultsFromLookup" + } + }; + + // Verify that $graphLookup can use the variable 'var1' which is defined in parent $lookup. + let res = local.aggregate([lookup]).toArray(); + assert.eq(res.length, 1); + assert.eq(res[0].resultsFromLookup.length, 1); + assert.eq(res[0].resultsFromLookup[0].resultsFromGraphLookup.length, 1); + assert.eq(res[0].resultsFromLookup[0].resultsFromGraphLookup[0], {_id: 0, from: "b", to: "a"}); + +})(); diff --git a/jstests/concurrency/fsm_all_sharded_with_stepdowns.js b/jstests/concurrency/fsm_all_sharded_with_stepdowns.js index af52a52123a..eb9a8a95727 100644 --- a/jstests/concurrency/fsm_all_sharded_with_stepdowns.js +++ b/jstests/concurrency/fsm_all_sharded_with_stepdowns.js @@ -133,6 +133,7 @@ var blacklist = [ // Use non retryable writes. 'remove_and_bulk_insert.js', + 'schema_validator_with_expr_variables.js', 'update_and_bulk_insert.js', 'update_check_index.js', 'update_multifield_isolated_multiupdate.js', diff --git a/jstests/concurrency/fsm_all_sharded_with_stepdowns_and_balancer.js b/jstests/concurrency/fsm_all_sharded_with_stepdowns_and_balancer.js index c67d0c85cce..25e7c25bfcc 100644 --- a/jstests/concurrency/fsm_all_sharded_with_stepdowns_and_balancer.js +++ b/jstests/concurrency/fsm_all_sharded_with_stepdowns_and_balancer.js @@ -139,6 +139,7 @@ var blacklist = [ // Use non retryable writes. 'remove_and_bulk_insert.js', + 'schema_validator_with_expr_variables.js', 'update_and_bulk_insert.js', 'update_check_index.js', 'update_multifield_isolated_multiupdate.js', diff --git a/jstests/concurrency/fsm_workloads/schema_validator_with_expr_variables.js b/jstests/concurrency/fsm_workloads/schema_validator_with_expr_variables.js new file mode 100644 index 00000000000..da39afdcf32 --- /dev/null +++ b/jstests/concurrency/fsm_workloads/schema_validator_with_expr_variables.js @@ -0,0 +1,77 @@ +/** + * Test to verify that the schema validator works correctly in a multi-threaded environment, when + * $expr uses expressions which mutate variable values while executing ($let, $map etc). + * @tags: [requires_non_retryable_writes] + */ + +"use strict"; + +var $config = (function() { + function setup(db, collName) { + for (let i = 0; i < 200; ++i) { + assertAlways.writeOK( + db[collName].insert({_id: i, a: i, one: 1, counter: 0, array: [0, i]})); + } + + // Add a validator which checks that field 'a' has value 5 and sum of the elements in field + // 'array' is 5. The expression is purposefully complex so that it can create a stress on + // expressions with variables. + assertAlways.commandWorked(db.runCommand({ + collMod: collName, + validator: { + $expr: { + $and: [ + { + $eq: [ + 5, + { + $let: { + vars: {item: {$multiply: ["$a", "$one"]}}, + in : {$multiply: ["$$item", "$one"]} + } + } + ] + }, + { + $eq: [ + 5, + { + $sum: { + $map: + {"input": "$array", "as": "item", "in": "$$item"} + } + } + ] + } + ] + } + } + })); + } + + const states = { + applyValidator: function(db, collName) { + assertAlways.writeOK(db[collName].update({_id: 5}, {$inc: {counter: 1}})); + assertAlways.writeError( + db[collName].update({_id: 4}, {$set: {a: 4}, $inc: {counter: 1}})); + + // Update all the documents in the collection. + assertAlways.writeOK(db[collName].update( + {}, {$set: {a: 5, array: [2, 3]}, $inc: {counter: 1}}, {multi: true})); + + // Validation fails when elements of 'array' doesn't add up to 5. + assertAlways.writeError(db[collName].update({_id: 4}, {$set: {a: 5, array: [2, 2]}})); + } + }; + + let transitions = {applyValidator: {applyValidator: 1}}; + + return { + threadCount: 50, + iterations: 100, + states: states, + startState: "applyValidator", + transitions: transitions, + setup: setup + }; +})(); diff --git a/src/mongo/db/matcher/expression_expr.cpp b/src/mongo/db/matcher/expression_expr.cpp index 9ba0567ac99..c4921cfb440 100644 --- a/src/mongo/db/matcher/expression_expr.cpp +++ b/src/mongo/db/matcher/expression_expr.cpp @@ -49,7 +49,12 @@ bool ExprMatchExpression::matches(const MatchableDocument* doc, MatchDetails* de } Document document(doc->toBSON()); - auto value = _expression->evaluate(document); + + // 'Variables' is not thread safe, and ExprMatchExpression may be used in a validator which + // processes documents from multiple threads simultaneously. Hence we make a copy of the + // 'Variables' object per-caller. + Variables variables = _expCtx->variables; + auto value = _expression->evaluate(document, &variables); return value.coerceToBool(); } diff --git a/src/mongo/db/pipeline/document_source_bucket_auto.cpp b/src/mongo/db/pipeline/document_source_bucket_auto.cpp index 9308328e7c6..1d347909881 100644 --- a/src/mongo/db/pipeline/document_source_bucket_auto.cpp +++ b/src/mongo/db/pipeline/document_source_bucket_auto.cpp @@ -158,7 +158,7 @@ Value DocumentSourceBucketAuto::extractKey(const Document& doc) { return Value(BSONNULL); } - Value key = _groupByExpression->evaluate(doc); + Value key = _groupByExpression->evaluate(doc, &pExpCtx->variables); if (_granularityRounder) { uassert(40258, @@ -191,7 +191,8 @@ void DocumentSourceBucketAuto::addDocumentToBucket(const pair<Value, Document>& const size_t numAccumulators = _accumulatedFields.size(); for (size_t k = 0; k < numAccumulators; k++) { - bucket._accums[k]->process(_accumulatedFields[k].expression->evaluate(entry.second), false); + bucket._accums[k]->process( + _accumulatedFields[k].expression->evaluate(entry.second, &pExpCtx->variables), false); } } diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.cpp b/src/mongo/db/pipeline/document_source_graph_lookup.cpp index cac5e520df2..7b1d338f620 100644 --- a/src/mongo/db/pipeline/document_source_graph_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_graph_lookup.cpp @@ -335,7 +335,7 @@ void DocumentSourceGraphLookUp::performSearch() { // Make sure _input is set before calling performSearch(). invariant(_input); - Value startingValue = _startWith->evaluate(*_input); + Value startingValue = _startWith->evaluate(*_input, &pExpCtx->variables); // If _startWith evaluates to an array, treat each value as a separate starting point. if (startingValue.isArray()) { diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp index ac83f74d998..c2bb0d1f98d 100644 --- a/src/mongo/db/pipeline/document_source_group.cpp +++ b/src/mongo/db/pipeline/document_source_group.cpp @@ -164,7 +164,10 @@ DocumentSource::GetNextResult DocumentSourceGroup::getNextStreaming() { // Add to the current accumulator(s). for (size_t i = 0; i < _currentAccumulators.size(); i++) { _currentAccumulators[i]->process( - _accumulatedFields[i].expression->evaluate(*_firstDocOfNextGroup), _doingMerge); + _accumulatedFields[i].expression->evaluate( + *_firstDocOfNextGroup, + &(_accumulatedFields[i].expression->getExpressionContext()->variables)), + _doingMerge); } // Retrieve the next document. @@ -555,8 +558,9 @@ DocumentSource::GetNextResult DocumentSourceGroup::initialize() { dassert(numAccumulators == group.size()); for (size_t i = 0; i < numAccumulators; i++) { - group[i]->process(_accumulatedFields[i].expression->evaluate(rootDocument), - _doingMerge); + group[i]->process( + _accumulatedFields[i].expression->evaluate(rootDocument, &pExpCtx->variables), + _doingMerge); _memoryUsageBytes += group[i]->memUsageForSorter(); } @@ -814,7 +818,7 @@ BSONObjSet DocumentSourceGroup::getOutputSorts() { Value DocumentSourceGroup::computeId(const Document& root) { // If only one expression, return result directly if (_idExpressions.size() == 1) { - Value retValue = _idExpressions[0]->evaluate(root); + Value retValue = _idExpressions[0]->evaluate(root, &pExpCtx->variables); return retValue.missing() ? Value(BSONNULL) : std::move(retValue); } @@ -822,7 +826,7 @@ Value DocumentSourceGroup::computeId(const Document& root) { vector<Value> vals; vals.reserve(_idExpressions.size()); for (size_t i = 0; i < _idExpressions.size(); i++) { - vals.push_back(_idExpressions[i]->evaluate(root)); + vals.push_back(_idExpressions[i]->evaluate(root, &pExpCtx->variables)); } return Value(std::move(vals)); } diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp index 04ca69ab9bc..444d61649ce 100644 --- a/src/mongo/db/pipeline/document_source_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_lookup.cpp @@ -603,7 +603,7 @@ void DocumentSourceLookUp::resolveLetVariables(const Document& localDoc, Variabl invariant(variables); for (auto& letVar : _letVariables) { - auto value = letVar.expression->evaluate(localDoc); + auto value = letVar.expression->evaluate(localDoc, &pExpCtx->variables); variables->setConstantValue(letVar.id, value); } } diff --git a/src/mongo/db/pipeline/document_source_redact.cpp b/src/mongo/db/pipeline/document_source_redact.cpp index c887c59f5cc..41c05b025ea 100644 --- a/src/mongo/db/pipeline/document_source_redact.cpp +++ b/src/mongo/db/pipeline/document_source_redact.cpp @@ -132,7 +132,7 @@ Value DocumentSourceRedact::redactValue(const Value& in, const Document& root) { boost::optional<Document> DocumentSourceRedact::redactObject(const Document& root) { auto& variables = pExpCtx->variables; - const Value expressionResult = _expression->evaluate(root); + const Value expressionResult = _expression->evaluate(root, &variables); ValueComparator simpleValueCmp; if (simpleValueCmp.evaluate(expressionResult == keepVal)) { diff --git a/src/mongo/db/pipeline/document_source_replace_root.cpp b/src/mongo/db/pipeline/document_source_replace_root.cpp index af2f46982d7..540c66c89a2 100644 --- a/src/mongo/db/pipeline/document_source_replace_root.cpp +++ b/src/mongo/db/pipeline/document_source_replace_root.cpp @@ -60,7 +60,7 @@ public: Document applyTransformation(const Document& input) final { // Extract subdocument in the form of a Value. - Value newRoot = _newRoot->evaluate(input); + Value newRoot = _newRoot->evaluate(input, &_expCtx->variables); // The newRoot expression, if it exists, must evaluate to an object. uassert(40228, diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp index 4f3821633c3..9dac5528de1 100644 --- a/src/mongo/db/pipeline/document_source_sort.cpp +++ b/src/mongo/db/pipeline/document_source_sort.cpp @@ -462,7 +462,7 @@ StatusWith<Value> DocumentSourceSort::extractKeyPart(const Document& doc, plainKey = key.getValue(); } else { invariant(patternPart.expression); - plainKey = patternPart.expression->evaluate(doc); + plainKey = patternPart.expression->evaluate(doc, &pExpCtx->variables); } return getCollationComparisonKey(plainKey); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index c800b1957ca..8acc6cb9c6a 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -241,7 +241,7 @@ const char* ExpressionAbs::getOpName() const { /* ------------------------- ExpressionAdd ----------------------------- */ -Value ExpressionAdd::evaluate(const Document& root) const { +Value ExpressionAdd::evaluate(const Document& root, Variables* variables) const { // We'll try to return the narrowest possible result value while avoiding overflow, loss // of precision due to intermediate rounding or implicit use of decimal types. To do that, // compute a compensated sum for non-decimal values and a separate decimal sum for decimal @@ -253,7 +253,7 @@ Value ExpressionAdd::evaluate(const Document& root) const { const size_t n = vpOperand.size(); for (size_t i = 0; i < n; ++i) { - Value val = vpOperand[i]->evaluate(root); + Value val = vpOperand[i]->evaluate(root, variables); switch (val.getType()) { case NumberDecimal: @@ -323,8 +323,8 @@ const char* ExpressionAdd::getOpName() const { /* ------------------------- ExpressionAllElementsTrue -------------------------- */ -Value ExpressionAllElementsTrue::evaluate(const Document& root) const { - const Value arr = vpOperand[0]->evaluate(root); +Value ExpressionAllElementsTrue::evaluate(const Document& root, Variables* variables) const { + const Value arr = vpOperand[0]->evaluate(root, variables); uassert(17040, str::stream() << getOpName() << "'s argument must be an array, but is " << typeName(arr.getType()), @@ -401,10 +401,10 @@ intrusive_ptr<Expression> ExpressionAnd::optimize() { return pE; } -Value ExpressionAnd::evaluate(const Document& root) const { +Value ExpressionAnd::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); for (size_t i = 0; i < n; ++i) { - Value pValue(vpOperand[i]->evaluate(root)); + Value pValue(vpOperand[i]->evaluate(root, variables)); if (!pValue.coerceToBool()) return Value(false); } @@ -419,8 +419,8 @@ const char* ExpressionAnd::getOpName() const { /* ------------------------- ExpressionAnyElementTrue -------------------------- */ -Value ExpressionAnyElementTrue::evaluate(const Document& root) const { - const Value arr = vpOperand[0]->evaluate(root); +Value ExpressionAnyElementTrue::evaluate(const Document& root, Variables* variables) const { + const Value arr = vpOperand[0]->evaluate(root, variables); uassert(17041, str::stream() << getOpName() << "'s argument must be an array, but is " << typeName(arr.getType()), @@ -441,11 +441,11 @@ const char* ExpressionAnyElementTrue::getOpName() const { /* ---------------------- ExpressionArray --------------------------- */ -Value ExpressionArray::evaluate(const Document& root) const { +Value ExpressionArray::evaluate(const Document& root, Variables* variables) const { vector<Value> values; values.reserve(vpOperand.size()); for (auto&& expr : vpOperand) { - Value elemVal = expr->evaluate(root); + Value elemVal = expr->evaluate(root, variables); values.push_back(elemVal.missing() ? Value(BSONNULL) : std::move(elemVal)); } return Value(std::move(values)); @@ -467,9 +467,9 @@ const char* ExpressionArray::getOpName() const { /* ------------------------- ExpressionArrayElemAt -------------------------- */ -Value ExpressionArrayElemAt::evaluate(const Document& root) const { - const Value array = vpOperand[0]->evaluate(root); - const Value indexArg = vpOperand[1]->evaluate(root); +Value ExpressionArrayElemAt::evaluate(const Document& root, Variables* variables) const { + const Value array = vpOperand[0]->evaluate(root, variables); + const Value indexArg = vpOperand[1]->evaluate(root, variables); if (array.nullish() || indexArg.nullish()) { return Value(BSONNULL); @@ -509,8 +509,9 @@ const char* ExpressionArrayElemAt::getOpName() const { /* ------------------------- ExpressionObjectToArray -------------------------- */ -Value ExpressionObjectToArray::evaluate(const Document& root) const { - const Value targetVal = vpOperand[0]->evaluate(root); + +Value ExpressionObjectToArray::evaluate(const Document& root, Variables* variables) const { + const Value targetVal = vpOperand[0]->evaluate(root, variables); if (targetVal.nullish()) { return Value(BSONNULL); @@ -541,8 +542,9 @@ const char* ExpressionObjectToArray::getOpName() const { } /* ------------------------- ExpressionArrayToObject -------------------------- */ -Value ExpressionArrayToObject::evaluate(const Document& root) const { - const Value input = vpOperand[0]->evaluate(root); + +Value ExpressionArrayToObject::evaluate(const Document& root, Variables* variables) const { + const Value input = vpOperand[0]->evaluate(root, variables); if (input.nullish()) { return Value(BSONNULL); } @@ -690,8 +692,8 @@ void ExpressionCoerceToBool::_doAddDependencies(DepsTracker* deps) const { pExpression->addDependencies(deps); } -Value ExpressionCoerceToBool::evaluate(const Document& root) const { - Value pResult(pExpression->evaluate(root)); +Value ExpressionCoerceToBool::evaluate(const Document& root, Variables* variables) const { + Value pResult(pExpression->evaluate(root, variables)); bool b = pResult.coerceToBool(); if (b) return Value(true); @@ -792,9 +794,10 @@ static const CmpLookup cmpLookup[7] = { }; } -Value ExpressionCompare::evaluate(const Document& root) const { - Value pLeft(vpOperand[0]->evaluate(root)); - Value pRight(vpOperand[1]->evaluate(root)); + +Value ExpressionCompare::evaluate(const Document& root, Variables* variables) const { + Value pLeft(vpOperand[0]->evaluate(root, variables)); + Value pRight(vpOperand[1]->evaluate(root, variables)); int cmp = getExpressionContext()->getValueComparator().compare(pLeft, pRight); @@ -820,12 +823,12 @@ const char* ExpressionCompare::getOpName() const { /* ------------------------- ExpressionConcat ----------------------------- */ -Value ExpressionConcat::evaluate(const Document& root) const { +Value ExpressionConcat::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); StringBuilder result; for (size_t i = 0; i < n; ++i) { - Value val = vpOperand[i]->evaluate(root); + Value val = vpOperand[i]->evaluate(root, variables); if (val.nullish()) return Value(BSONNULL); @@ -846,12 +849,12 @@ const char* ExpressionConcat::getOpName() const { /* ------------------------- ExpressionConcatArrays ----------------------------- */ -Value ExpressionConcatArrays::evaluate(const Document& root) const { +Value ExpressionConcatArrays::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); vector<Value> values; for (size_t i = 0; i < n; ++i) { - Value val = vpOperand[i]->evaluate(root); + Value val = vpOperand[i]->evaluate(root, variables); if (val.nullish()) { return Value(BSONNULL); } @@ -874,10 +877,10 @@ const char* ExpressionConcatArrays::getOpName() const { /* ----------------------- ExpressionCond ------------------------------ */ -Value ExpressionCond::evaluate(const Document& root) const { - Value pCond(vpOperand[0]->evaluate(root)); +Value ExpressionCond::evaluate(const Document& root, Variables* variables) const { + Value pCond(vpOperand[0]->evaluate(root, variables)); int idx = pCond.coerceToBool() ? 1 : 2; - return vpOperand[idx]->evaluate(root); + return vpOperand[idx]->evaluate(root, variables); } intrusive_ptr<Expression> ExpressionCond::parse( @@ -948,7 +951,7 @@ void ExpressionConstant::_doAddDependencies(DepsTracker* deps) const { /* nothing to do */ } -Value ExpressionConstant::evaluate(const Document& root) const { +Value ExpressionConstant::evaluate(const Document& root, Variables* variables) const { return _value; } @@ -970,14 +973,15 @@ namespace { boost::optional<TimeZone> makeTimeZone(const TimeZoneDatabase* tzdb, const Document& root, - const Expression* timeZone) { + const Expression* timeZone, + Variables* variables) { invariant(tzdb); if (!timeZone) { return mongo::TimeZoneDatabase::utcZone(); } - auto timeZoneId = timeZone->evaluate(root); + auto timeZoneId = timeZone->evaluate(root, variables); if (timeZoneId.nullish()) { return boost::none; @@ -1148,8 +1152,10 @@ intrusive_ptr<Expression> ExpressionDateFromParts::optimize() { _isoWeek, _isoDayOfWeek, _timeZone})) { + // Everything is a constant, so we can turn into a constant. - return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables))); } return this; @@ -1191,13 +1197,14 @@ bool ExpressionDateFromParts::evaluateNumberWithinRange(const Document& root, int defaultValue, int minValue, int maxValue, - int* returnValue) const { + int* returnValue, + Variables* variables) const { if (!field) { *returnValue = defaultValue; return true; } - auto fieldValue = field->evaluate(root); + auto fieldValue = field->evaluate(root, variables); if (fieldValue.nullish()) { return false; @@ -1224,18 +1231,21 @@ bool ExpressionDateFromParts::evaluateNumberWithinRange(const Document& root, return true; } -Value ExpressionDateFromParts::evaluate(const Document& root) const { +Value ExpressionDateFromParts::evaluate(const Document& root, Variables* variables) const { int hour, minute, second, millisecond; - if (!evaluateNumberWithinRange(root, _hour.get(), "hour"_sd, 0, 0, 24, &hour) || - !evaluateNumberWithinRange(root, _minute.get(), "minute"_sd, 0, 0, 59, &minute) || - !evaluateNumberWithinRange(root, _second.get(), "second"_sd, 0, 0, 59, &second) || + if (!evaluateNumberWithinRange(root, _hour.get(), "hour"_sd, 0, 0, 24, &hour, variables) || + !evaluateNumberWithinRange( + root, _minute.get(), "minute"_sd, 0, 0, 59, &minute, variables) || + !evaluateNumberWithinRange( + root, _second.get(), "second"_sd, 0, 0, 59, &second, variables) || !evaluateNumberWithinRange( - root, _millisecond.get(), "millisecond"_sd, 0, 0, 999, &millisecond)) { + root, _millisecond.get(), "millisecond"_sd, 0, 0, 999, &millisecond, variables)) { return Value(BSONNULL); } - auto timeZone = makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get()); + auto timeZone = + makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get(), variables); if (!timeZone) { return Value(BSONNULL); @@ -1244,9 +1254,11 @@ Value ExpressionDateFromParts::evaluate(const Document& root) const { if (_year) { int year, month, day; - if (!evaluateNumberWithinRange(root, _year.get(), "year"_sd, 1970, 0, 9999, &year) || - !evaluateNumberWithinRange(root, _month.get(), "month"_sd, 1, 1, 12, &month) || - !evaluateNumberWithinRange(root, _day.get(), "day"_sd, 1, 1, 31, &day)) { + if (!evaluateNumberWithinRange( + root, _year.get(), "year"_sd, 1970, 0, 9999, &year, variables) || + !evaluateNumberWithinRange( + root, _month.get(), "month"_sd, 1, 1, 12, &month, variables) || + !evaluateNumberWithinRange(root, _day.get(), "day"_sd, 1, 1, 31, &day, variables)) { return Value(BSONNULL); } @@ -1257,11 +1269,18 @@ Value ExpressionDateFromParts::evaluate(const Document& root) const { if (_isoWeekYear) { int isoWeekYear, isoWeek, isoDayOfWeek; - if (!evaluateNumberWithinRange( - root, _isoWeekYear.get(), "isoWeekYear"_sd, 1970, 0, 9999, &isoWeekYear) || - !evaluateNumberWithinRange(root, _isoWeek.get(), "isoWeek"_sd, 1, 1, 53, &isoWeek) || + if (!evaluateNumberWithinRange(root, + _isoWeekYear.get(), + "isoWeekYear"_sd, + 1970, + 0, + 9999, + &isoWeekYear, + variables) || !evaluateNumberWithinRange( - root, _isoDayOfWeek.get(), "isoDayOfWeek"_sd, 1, 1, 7, &isoDayOfWeek)) { + root, _isoWeek.get(), "isoWeek"_sd, 1, 1, 53, &isoWeek, variables) || + !evaluateNumberWithinRange( + root, _isoDayOfWeek.get(), "isoDayOfWeek"_sd, 1, 1, 7, &isoDayOfWeek, variables)) { return Value(BSONNULL); } @@ -1361,7 +1380,8 @@ intrusive_ptr<Expression> ExpressionDateFromString::optimize() { if (ExpressionConstant::allNullOrConstant({_dateString, _timeZone})) { // Everything is a constant, so we can turn into a constant. - return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables))); } return this; } @@ -1373,10 +1393,11 @@ Value ExpressionDateFromString::serialize(bool explain) const { {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}}); } -Value ExpressionDateFromString::evaluate(const Document& root) const { - const Value dateString = _dateString->evaluate(root); +Value ExpressionDateFromString::evaluate(const Document& root, Variables* variables) const { + const Value dateString = _dateString->evaluate(root, variables); - auto timeZone = makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get()); + auto timeZone = + makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get(), variables); if (!timeZone || dateString.nullish()) { return Value(BSONNULL); @@ -1462,7 +1483,8 @@ intrusive_ptr<Expression> ExpressionDateToParts::optimize() { if (ExpressionConstant::allNullOrConstant({_date, _iso8601, _timeZone})) { // Everything is a constant, so we can turn into a constant. - return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables))); } return this; @@ -1476,12 +1498,13 @@ Value ExpressionDateToParts::serialize(bool explain) const { {"iso8601", _iso8601 ? _iso8601->serialize(explain) : Value()}}}}); } -boost::optional<int> ExpressionDateToParts::evaluateIso8601Flag(const Document& root) const { +boost::optional<int> ExpressionDateToParts::evaluateIso8601Flag(const Document& root, + Variables* variables) const { if (!_iso8601) { return false; } - auto iso8601Output = _iso8601->evaluate(root); + auto iso8601Output = _iso8601->evaluate(root, variables); if (iso8601Output.nullish()) { return boost::none; @@ -1495,15 +1518,16 @@ boost::optional<int> ExpressionDateToParts::evaluateIso8601Flag(const Document& return iso8601Output.getBool(); } -Value ExpressionDateToParts::evaluate(const Document& root) const { - const Value date = _date->evaluate(root); +Value ExpressionDateToParts::evaluate(const Document& root, Variables* variables) const { + const Value date = _date->evaluate(root, variables); - auto timeZone = makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get()); + auto timeZone = + makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get(), variables); if (!timeZone) { return Value(BSONNULL); } - auto iso8601 = evaluateIso8601Flag(root); + auto iso8601 = evaluateIso8601Flag(root, variables); if (!iso8601) { return Value(BSONNULL); } @@ -1608,7 +1632,8 @@ intrusive_ptr<Expression> ExpressionDateToString::optimize() { if (ExpressionConstant::allNullOrConstant({_date, _timeZone})) { // Everything is a constant, so we can turn into a constant. - return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables))); } return this; @@ -1622,10 +1647,11 @@ Value ExpressionDateToString::serialize(bool explain) const { {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}}); } -Value ExpressionDateToString::evaluate(const Document& root) const { - const Value date = _date->evaluate(root); +Value ExpressionDateToString::evaluate(const Document& root, Variables* variables) const { + const Value date = _date->evaluate(root, variables); - auto timeZone = makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get()); + auto timeZone = + makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get(), variables); if (!timeZone) { return Value(BSONNULL); } @@ -1646,9 +1672,9 @@ void ExpressionDateToString::_doAddDependencies(DepsTracker* deps) const { /* ----------------------- ExpressionDivide ---------------------------- */ -Value ExpressionDivide::evaluate(const Document& root) const { - Value lhs = vpOperand[0]->evaluate(root); - Value rhs = vpOperand[1]->evaluate(root); +Value ExpressionDivide::evaluate(const Document& root, Variables* variables) const { + Value lhs = vpOperand[0]->evaluate(root, variables); + Value rhs = vpOperand[1]->evaluate(root, variables); auto assertNonZero = [](bool nonZero) { uassert(16608, "can't $divide by zero", nonZero); }; @@ -1747,10 +1773,10 @@ void ExpressionObject::_doAddDependencies(DepsTracker* deps) const { } } -Value ExpressionObject::evaluate(const Document& root) const { +Value ExpressionObject::evaluate(const Document& root, Variables* variables) const { MutableDocument outputDoc; for (auto&& pair : _expressions) { - outputDoc.addField(pair.first, pair.second->evaluate(root)); + outputDoc.addField(pair.first, pair.second->evaluate(root, variables)); } return outputDoc.freezeToValue(); } @@ -1826,7 +1852,8 @@ intrusive_ptr<Expression> ExpressionFieldPath::optimize() { } if (getExpressionContext()->variables.hasConstantValue(_variable)) { - return ExpressionConstant::create(getExpressionContext(), evaluate(Document())); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document(), &(getExpressionContext()->variables))); } return intrusive_ptr<Expression>(this); @@ -1883,17 +1910,16 @@ Value ExpressionFieldPath::evaluatePath(size_t index, const Document& input) con } } -Value ExpressionFieldPath::evaluate(const Document& root) const { - auto& vars = getExpressionContext()->variables; +Value ExpressionFieldPath::evaluate(const Document& root, Variables* variables) const { if (_fieldPath.getPathLength() == 1) // get the whole variable - return vars.getValue(_variable, root); + return variables->getValue(_variable, root); if (_variable == Variables::kRootId) { // ROOT is always a document so use optimized code path return evaluatePath(1, root); } - Value var = vars.getValue(_variable, root); + Value var = variables->getValue(_variable, root); switch (var.getType()) { case Object: return evaluatePath(1, var.getDocument()); @@ -2012,9 +2038,9 @@ Value ExpressionFilter::serialize(bool explain) const { << _filter->serialize(explain)))); } -Value ExpressionFilter::evaluate(const Document& root) const { +Value ExpressionFilter::evaluate(const Document& root, Variables* variables) const { // We are guaranteed at parse time that this isn't using our _varId. - const Value inputVal = _input->evaluate(root); + const Value inputVal = _input->evaluate(root, variables); if (inputVal.nullish()) return Value(BSONNULL); @@ -2029,11 +2055,10 @@ Value ExpressionFilter::evaluate(const Document& root) const { return inputVal; vector<Value> output; - auto& vars = getExpressionContext()->variables; for (const auto& elem : input) { - vars.setValue(_varId, elem); + variables->setValue(_varId, elem); - if (_filter->evaluate(root).coerceToBool()) { + if (_filter->evaluate(root, variables).coerceToBool()) { output.push_back(std::move(elem)); } } @@ -2145,15 +2170,14 @@ Value ExpressionLet::serialize(bool explain) const { DOC("$let" << DOC("vars" << vars.freeze() << "in" << _subExpression->serialize(explain)))); } -Value ExpressionLet::evaluate(const Document& root) const { +Value ExpressionLet::evaluate(const Document& root, Variables* variables) const { for (const auto& item : _variables) { // It is guaranteed at parse-time that these expressions don't use the variable ids we // are setting - getExpressionContext()->variables.setValue(item.first, - item.second.expression->evaluate(root)); + variables->setValue(item.first, item.second.expression->evaluate(root, variables)); } - return _subExpression->evaluate(root); + return _subExpression->evaluate(root, variables); } void ExpressionLet::_doAddDependencies(DepsTracker* deps) const { @@ -2237,9 +2261,9 @@ Value ExpressionMap::serialize(bool explain) const { << _each->serialize(explain)))); } -Value ExpressionMap::evaluate(const Document& root) const { +Value ExpressionMap::evaluate(const Document& root, Variables* variables) const { // guaranteed at parse time that this isn't using our _varId - const Value inputVal = _input->evaluate(root); + const Value inputVal = _input->evaluate(root, variables); if (inputVal.nullish()) return Value(BSONNULL); @@ -2255,9 +2279,9 @@ Value ExpressionMap::evaluate(const Document& root) const { vector<Value> output; output.reserve(input.size()); for (size_t i = 0; i < input.size(); i++) { - getExpressionContext()->variables.setValue(_varId, input[i]); + variables->setValue(_varId, input[i]); - Value toInsert = _each->evaluate(root); + Value toInsert = _each->evaluate(root, variables); if (toInsert.missing()) toInsert = Value(BSONNULL); // can't insert missing values into array @@ -2334,7 +2358,7 @@ Value ExpressionMeta::serialize(bool explain) const { MONGO_UNREACHABLE; } -Value ExpressionMeta::evaluate(const Document& root) const { +Value ExpressionMeta::evaluate(const Document& root, Variables* variables) const { switch (_metaType) { case MetaType::TEXT_SCORE: return root.hasTextScore() ? Value(root.getTextScore()) : Value(); @@ -2352,9 +2376,9 @@ void ExpressionMeta::_doAddDependencies(DepsTracker* deps) const { /* ----------------------- ExpressionMod ---------------------------- */ -Value ExpressionMod::evaluate(const Document& root) const { - Value lhs = vpOperand[0]->evaluate(root); - Value rhs = vpOperand[1]->evaluate(root); +Value ExpressionMod::evaluate(const Document& root, Variables* variables) const { + Value lhs = vpOperand[0]->evaluate(root, variables); + Value rhs = vpOperand[1]->evaluate(root, variables); BSONType leftType = lhs.getType(); BSONType rightType = rhs.getType(); @@ -2409,7 +2433,7 @@ const char* ExpressionMod::getOpName() const { /* ------------------------- ExpressionMultiply ----------------------------- */ -Value ExpressionMultiply::evaluate(const Document& root) const { +Value ExpressionMultiply::evaluate(const Document& root, Variables* variables) const { /* We'll try to return the narrowest possible result value. To do that without creating intermediate Values, do the arithmetic for double @@ -2424,7 +2448,7 @@ Value ExpressionMultiply::evaluate(const Document& root) const { const size_t n = vpOperand.size(); for (size_t i = 0; i < n; ++i) { - Value val = vpOperand[i]->evaluate(root); + Value val = vpOperand[i]->evaluate(root, variables); if (val.numeric()) { BSONType oldProductType = productType; @@ -2472,12 +2496,12 @@ const char* ExpressionMultiply::getOpName() const { /* ----------------------- ExpressionIfNull ---------------------------- */ -Value ExpressionIfNull::evaluate(const Document& root) const { - Value pLeft(vpOperand[0]->evaluate(root)); +Value ExpressionIfNull::evaluate(const Document& root, Variables* variables) const { + Value pLeft(vpOperand[0]->evaluate(root, variables)); if (!pLeft.nullish()) return pLeft; - Value pRight(vpOperand[1]->evaluate(root)); + Value pRight(vpOperand[1]->evaluate(root, variables)); return pRight; } @@ -2488,9 +2512,9 @@ const char* ExpressionIfNull::getOpName() const { /* ----------------------- ExpressionIn ---------------------------- */ -Value ExpressionIn::evaluate(const Document& root) const { - Value argument(vpOperand[0]->evaluate(root)); - Value arrayOfValues(vpOperand[1]->evaluate(root)); +Value ExpressionIn::evaluate(const Document& root, Variables* variables) const { + Value argument(vpOperand[0]->evaluate(root, variables)); + Value arrayOfValues(vpOperand[1]->evaluate(root, variables)); uassert(40081, str::stream() << "$in requires an array as a second argument, found: " @@ -2532,8 +2556,8 @@ void uassertIfNotIntegralAndNonNegative(Value val, } // namespace -Value ExpressionIndexOfArray::evaluate(const Document& root) const { - Value arrayArg = vpOperand[0]->evaluate(root); +Value ExpressionIndexOfArray::evaluate(const Document& root, Variables* variables) const { + Value arrayArg = vpOperand[0]->evaluate(root, variables); if (arrayArg.nullish()) { return Value(BSONNULL); @@ -2546,18 +2570,18 @@ Value ExpressionIndexOfArray::evaluate(const Document& root) const { std::vector<Value> array = arrayArg.getArray(); - Value searchItem = vpOperand[1]->evaluate(root); + Value searchItem = vpOperand[1]->evaluate(root, variables); size_t startIndex = 0; if (vpOperand.size() > 2) { - Value startIndexArg = vpOperand[2]->evaluate(root); + Value startIndexArg = vpOperand[2]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(startIndexArg, getOpName(), "starting index"); startIndex = static_cast<size_t>(startIndexArg.coerceToInt()); } size_t endIndex = array.size(); if (vpOperand.size() > 3) { - Value endIndexArg = vpOperand[3]->evaluate(root); + Value endIndexArg = vpOperand[3]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(endIndexArg, getOpName(), "ending index"); // Don't let 'endIndex' exceed the length of the array. endIndex = std::min(array.size(), static_cast<size_t>(endIndexArg.coerceToInt())); @@ -2590,8 +2614,8 @@ bool stringHasTokenAtIndex(size_t index, const std::string& input, const std::st } // namespace -Value ExpressionIndexOfBytes::evaluate(const Document& root) const { - Value stringArg = vpOperand[0]->evaluate(root); +Value ExpressionIndexOfBytes::evaluate(const Document& root, Variables* variables) const { + Value stringArg = vpOperand[0]->evaluate(root, variables); if (stringArg.nullish()) { return Value(BSONNULL); @@ -2603,7 +2627,7 @@ Value ExpressionIndexOfBytes::evaluate(const Document& root) const { stringArg.getType() == String); const std::string& input = stringArg.getString(); - Value tokenArg = vpOperand[1]->evaluate(root); + Value tokenArg = vpOperand[1]->evaluate(root, variables); uassert(40092, str::stream() << "$indexOfBytes requires a string as the second argument, found: " << typeName(tokenArg.getType()), @@ -2612,14 +2636,14 @@ Value ExpressionIndexOfBytes::evaluate(const Document& root) const { size_t startIndex = 0; if (vpOperand.size() > 2) { - Value startIndexArg = vpOperand[2]->evaluate(root); + Value startIndexArg = vpOperand[2]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(startIndexArg, getOpName(), "starting index"); startIndex = static_cast<size_t>(startIndexArg.coerceToInt()); } size_t endIndex = input.size(); if (vpOperand.size() > 3) { - Value endIndexArg = vpOperand[3]->evaluate(root); + Value endIndexArg = vpOperand[3]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(endIndexArg, getOpName(), "ending index"); // Don't let 'endIndex' exceed the length of the string. endIndex = std::min(input.size(), static_cast<size_t>(endIndexArg.coerceToInt())); @@ -2644,8 +2668,8 @@ const char* ExpressionIndexOfBytes::getOpName() const { /* ----------------------- ExpressionIndexOfCP --------------------- */ -Value ExpressionIndexOfCP::evaluate(const Document& root) const { - Value stringArg = vpOperand[0]->evaluate(root); +Value ExpressionIndexOfCP::evaluate(const Document& root, Variables* variables) const { + Value stringArg = vpOperand[0]->evaluate(root, variables); if (stringArg.nullish()) { return Value(BSONNULL); @@ -2657,7 +2681,7 @@ Value ExpressionIndexOfCP::evaluate(const Document& root) const { stringArg.getType() == String); const std::string& input = stringArg.getString(); - Value tokenArg = vpOperand[1]->evaluate(root); + Value tokenArg = vpOperand[1]->evaluate(root, variables); uassert(40094, str::stream() << "$indexOfCP requires a string as the second argument, found: " << typeName(tokenArg.getType()), @@ -2666,7 +2690,7 @@ Value ExpressionIndexOfCP::evaluate(const Document& root) const { size_t startCodePointIndex = 0; if (vpOperand.size() > 2) { - Value startIndexArg = vpOperand[2]->evaluate(root); + Value startIndexArg = vpOperand[2]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(startIndexArg, getOpName(), "starting index"); startCodePointIndex = static_cast<size_t>(startIndexArg.coerceToInt()); } @@ -2689,7 +2713,7 @@ Value ExpressionIndexOfCP::evaluate(const Document& root) const { size_t endCodePointIndex = codePointLength; if (vpOperand.size() > 3) { - Value endIndexArg = vpOperand[3]->evaluate(root); + Value endIndexArg = vpOperand[3]->evaluate(root, variables); uassertIfNotIntegralAndNonNegative(endIndexArg, getOpName(), "ending index"); // Don't let 'endCodePointIndex' exceed the number of code points in the string. @@ -2746,9 +2770,9 @@ const char* ExpressionLn::getOpName() const { /* ----------------------- ExpressionLog ---------------------------- */ -Value ExpressionLog::evaluate(const Document& root) const { - Value argVal = vpOperand[0]->evaluate(root); - Value baseVal = vpOperand[1]->evaluate(root); +Value ExpressionLog::evaluate(const Document& root, Variables* variables) const { + Value argVal = vpOperand[0]->evaluate(root, variables); + Value baseVal = vpOperand[1]->evaluate(root, variables); if (argVal.nullish() || baseVal.nullish()) return Value(BSONNULL); @@ -2840,8 +2864,8 @@ intrusive_ptr<Expression> ExpressionNary::optimize() { // If all the operands are constant expressions, collapse the expression into one constant // expression. if (constOperandCount == vpOperand.size()) { - return intrusive_ptr<Expression>( - ExpressionConstant::create(getExpressionContext(), evaluate(Document()))); + return intrusive_ptr<Expression>(ExpressionConstant::create( + getExpressionContext(), evaluate(Document(), &(getExpressionContext()->variables)))); } // If the expression is associative, we can collapse all the consecutive constant operands into @@ -2884,8 +2908,9 @@ intrusive_ptr<Expression> ExpressionNary::optimize() { if (constExpressions.size() > 1) { ExpressionVector vpOperandSave = std::move(vpOperand); vpOperand = std::move(constExpressions); - optimizedOperands.emplace_back( - ExpressionConstant::create(getExpressionContext(), evaluate(Document()))); + optimizedOperands.emplace_back(ExpressionConstant::create( + getExpressionContext(), + evaluate(Document(), &getExpressionContext()->variables))); vpOperand = std::move(vpOperandSave); } else { optimizedOperands.insert( @@ -2899,8 +2924,9 @@ intrusive_ptr<Expression> ExpressionNary::optimize() { if (constExpressions.size() > 1) { vpOperand = std::move(constExpressions); - optimizedOperands.emplace_back( - ExpressionConstant::create(getExpressionContext(), evaluate(Document()))); + optimizedOperands.emplace_back(ExpressionConstant::create( + getExpressionContext(), + evaluate(Document(), &(getExpressionContext()->variables)))); } else { optimizedOperands.insert( optimizedOperands.end(), constExpressions.begin(), constExpressions.end()); @@ -2933,8 +2959,8 @@ Value ExpressionNary::serialize(bool explain) const { /* ------------------------- ExpressionNot ----------------------------- */ -Value ExpressionNot::evaluate(const Document& root) const { - Value pOp(vpOperand[0]->evaluate(root)); +Value ExpressionNot::evaluate(const Document& root, Variables* variables) const { + Value pOp(vpOperand[0]->evaluate(root, variables)); bool b = pOp.coerceToBool(); return Value(!b); @@ -2947,10 +2973,10 @@ const char* ExpressionNot::getOpName() const { /* -------------------------- ExpressionOr ----------------------------- */ -Value ExpressionOr::evaluate(const Document& root) const { +Value ExpressionOr::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); for (size_t i = 0; i < n; ++i) { - Value pValue(vpOperand[i]->evaluate(root)); + Value pValue(vpOperand[i]->evaluate(root, variables)); if (pValue.coerceToBool()) return Value(true); } @@ -3120,9 +3146,9 @@ intrusive_ptr<Expression> ExpressionPow::create( return expr; } -Value ExpressionPow::evaluate(const Document& root) const { - Value baseVal = vpOperand[0]->evaluate(root); - Value expVal = vpOperand[1]->evaluate(root); +Value ExpressionPow::evaluate(const Document& root, Variables* variables) const { + Value baseVal = vpOperand[0]->evaluate(root, variables); + Value expVal = vpOperand[1]->evaluate(root, variables); if (baseVal.nullish() || expVal.nullish()) return Value(BSONNULL); @@ -3238,9 +3264,9 @@ const char* ExpressionPow::getOpName() const { /* ------------------------- ExpressionRange ------------------------------ */ -Value ExpressionRange::evaluate(const Document& root) const { - Value startVal(vpOperand[0]->evaluate(root)); - Value endVal(vpOperand[1]->evaluate(root)); +Value ExpressionRange::evaluate(const Document& root, Variables* variables) const { + Value startVal(vpOperand[0]->evaluate(root, variables)); + Value endVal(vpOperand[1]->evaluate(root, variables)); uassert(34443, str::stream() << "$range requires a numeric starting value, found value of type: " @@ -3267,7 +3293,7 @@ Value ExpressionRange::evaluate(const Document& root) const { int step = 1; if (vpOperand.size() == 3) { // A step was specified by the user. - Value stepVal(vpOperand[2]->evaluate(root)); + Value stepVal(vpOperand[2]->evaluate(root, variables)); uassert(34447, str::stream() << "$range requires a numeric step value, found value of type:" @@ -3338,8 +3364,8 @@ intrusive_ptr<Expression> ExpressionReduce::parse( return reduce; } -Value ExpressionReduce::evaluate(const Document& root) const { - Value inputVal = _input->evaluate(root); +Value ExpressionReduce::evaluate(const Document& root, Variables* variables) const { + Value inputVal = _input->evaluate(root, variables); if (inputVal.nullish()) { return Value(BSONNULL); @@ -3350,14 +3376,13 @@ Value ExpressionReduce::evaluate(const Document& root) const { << inputVal.toString(), inputVal.isArray()); - Value accumulatedValue = _initial->evaluate(root); - auto& vars = getExpressionContext()->variables; + Value accumulatedValue = _initial->evaluate(root, variables); for (auto&& elem : inputVal.getArray()) { - vars.setValue(_thisVar, elem); - vars.setValue(_valueVar, accumulatedValue); + variables->setValue(_thisVar, elem); + variables->setValue(_valueVar, accumulatedValue); - accumulatedValue = _in->evaluate(root); + accumulatedValue = _in->evaluate(root, variables); } return accumulatedValue; @@ -3385,8 +3410,8 @@ Value ExpressionReduce::serialize(bool explain) const { /* ------------------------ ExpressionReverseArray ------------------------ */ -Value ExpressionReverseArray::evaluate(const Document& root) const { - Value input(vpOperand[0]->evaluate(root)); +Value ExpressionReverseArray::evaluate(const Document& root, Variables* variables) const { + Value input(vpOperand[0]->evaluate(root, variables)); if (input.nullish()) { return Value(BSONNULL); @@ -3422,9 +3447,9 @@ ValueSet arrayToSet(const Value& val, const ValueComparator& valueComparator) { /* ----------------------- ExpressionSetDifference ---------------------------- */ -Value ExpressionSetDifference::evaluate(const Document& root) const { - const Value lhs = vpOperand[0]->evaluate(root); - const Value rhs = vpOperand[1]->evaluate(root); +Value ExpressionSetDifference::evaluate(const Document& root, Variables* variables) const { + const Value lhs = vpOperand[0]->evaluate(root, variables); + const Value rhs = vpOperand[1]->evaluate(root, variables); if (lhs.nullish() || rhs.nullish()) { return Value(BSONNULL); @@ -3468,13 +3493,13 @@ void ExpressionSetEquals::validateArguments(const ExpressionVector& args) const args.size() >= 2); } -Value ExpressionSetEquals::evaluate(const Document& root) const { +Value ExpressionSetEquals::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); const auto& valueComparator = getExpressionContext()->getValueComparator(); ValueSet lhs = valueComparator.makeOrderedValueSet(); for (size_t i = 0; i < n; i++) { - const Value nextEntry = vpOperand[i]->evaluate(root); + const Value nextEntry = vpOperand[i]->evaluate(root, variables); uassert(17044, str::stream() << "All operands of $setEquals must be arrays. One " << "argument is of type: " @@ -3505,12 +3530,12 @@ const char* ExpressionSetEquals::getOpName() const { /* ----------------------- ExpressionSetIntersection ---------------------------- */ -Value ExpressionSetIntersection::evaluate(const Document& root) const { +Value ExpressionSetIntersection::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); const auto& valueComparator = getExpressionContext()->getValueComparator(); ValueSet currentIntersection = valueComparator.makeOrderedValueSet(); for (size_t i = 0; i < n; i++) { - const Value nextEntry = vpOperand[i]->evaluate(root); + const Value nextEntry = vpOperand[i]->evaluate(root, variables); if (nextEntry.nullish()) { return Value(BSONNULL); } @@ -3566,9 +3591,9 @@ Value setIsSubsetHelper(const vector<Value>& lhs, const ValueSet& rhs) { } } -Value ExpressionSetIsSubset::evaluate(const Document& root) const { - const Value lhs = vpOperand[0]->evaluate(root); - const Value rhs = vpOperand[1]->evaluate(root); +Value ExpressionSetIsSubset::evaluate(const Document& root, Variables* variables) const { + const Value lhs = vpOperand[0]->evaluate(root, variables); + const Value rhs = vpOperand[1]->evaluate(root, variables); uassert(17046, str::stream() << "both operands of $setIsSubset must be arrays. First " @@ -3601,8 +3626,8 @@ public: vpOperand = operands; } - virtual Value evaluate(const Document& root) const { - const Value lhs = vpOperand[0]->evaluate(root); + virtual Value evaluate(const Document& root, Variables* variables) const { + const Value lhs = vpOperand[0]->evaluate(root, variables); uassert(17310, str::stream() << "both operands of $setIsSubset must be arrays. First " @@ -3649,11 +3674,11 @@ const char* ExpressionSetIsSubset::getOpName() const { /* ----------------------- ExpressionSetUnion ---------------------------- */ -Value ExpressionSetUnion::evaluate(const Document& root) const { +Value ExpressionSetUnion::evaluate(const Document& root, Variables* variables) const { ValueSet unionedSet = getExpressionContext()->getValueComparator().makeOrderedValueSet(); const size_t n = vpOperand.size(); for (size_t i = 0; i < n; i++) { - const Value newEntries = vpOperand[i]->evaluate(root); + const Value newEntries = vpOperand[i]->evaluate(root, variables); if (newEntries.nullish()) { return Value(BSONNULL); } @@ -3675,8 +3700,8 @@ const char* ExpressionSetUnion::getOpName() const { /* ----------------------- ExpressionIsArray ---------------------------- */ -Value ExpressionIsArray::evaluate(const Document& root) const { - Value argument = vpOperand[0]->evaluate(root); +Value ExpressionIsArray::evaluate(const Document& root, Variables* variables) const { + Value argument = vpOperand[0]->evaluate(root, variables); return Value(argument.isArray()); } @@ -3687,12 +3712,12 @@ const char* ExpressionIsArray::getOpName() const { /* ----------------------- ExpressionSlice ---------------------------- */ -Value ExpressionSlice::evaluate(const Document& root) const { +Value ExpressionSlice::evaluate(const Document& root, Variables* variables) const { const size_t n = vpOperand.size(); - Value arrayVal = vpOperand[0]->evaluate(root); + Value arrayVal = vpOperand[0]->evaluate(root, variables); // Could be either a start index or the length from 0. - Value arg2 = vpOperand[1]->evaluate(root); + Value arg2 = vpOperand[1]->evaluate(root, variables); if (arrayVal.nullish() || arg2.nullish()) { return Value(BSONNULL); @@ -3743,7 +3768,7 @@ Value ExpressionSlice::evaluate(const Document& root) const { start = std::min(array.size(), size_t(startInt)); } - Value countVal = vpOperand[2]->evaluate(root); + Value countVal = vpOperand[2]->evaluate(root, variables); if (countVal.nullish()) { return Value(BSONNULL); @@ -3778,8 +3803,8 @@ const char* ExpressionSlice::getOpName() const { /* ----------------------- ExpressionSize ---------------------------- */ -Value ExpressionSize::evaluate(const Document& root) const { - Value array = vpOperand[0]->evaluate(root); +Value ExpressionSize::evaluate(const Document& root, Variables* variables) const { + Value array = vpOperand[0]->evaluate(root, variables); uassert(17124, str::stream() << "The argument to $size must be an array, but was of type: " @@ -3795,9 +3820,9 @@ const char* ExpressionSize::getOpName() const { /* ----------------------- ExpressionSplit --------------------------- */ -Value ExpressionSplit::evaluate(const Document& root) const { - Value inputArg = vpOperand[0]->evaluate(root); - Value separatorArg = vpOperand[1]->evaluate(root); +Value ExpressionSplit::evaluate(const Document& root, Variables* variables) const { + Value inputArg = vpOperand[0]->evaluate(root, variables); + Value separatorArg = vpOperand[1]->evaluate(root, variables); if (inputArg.nullish() || separatorArg.nullish()) { return Value(BSONNULL); @@ -3873,9 +3898,9 @@ const char* ExpressionSqrt::getOpName() const { /* ----------------------- ExpressionStrcasecmp ---------------------------- */ -Value ExpressionStrcasecmp::evaluate(const Document& root) const { - Value pString1(vpOperand[0]->evaluate(root)); - Value pString2(vpOperand[1]->evaluate(root)); +Value ExpressionStrcasecmp::evaluate(const Document& root, Variables* variables) const { + Value pString1(vpOperand[0]->evaluate(root, variables)); + Value pString2(vpOperand[1]->evaluate(root, variables)); /* boost::iequals returns a bool not an int so strings must actually be allocated */ string str1 = boost::to_upper_copy(pString1.coerceToString()); @@ -3897,10 +3922,10 @@ const char* ExpressionStrcasecmp::getOpName() const { /* ----------------------- ExpressionSubstrBytes ---------------------------- */ -Value ExpressionSubstrBytes::evaluate(const Document& root) const { - Value pString(vpOperand[0]->evaluate(root)); - Value pLower(vpOperand[1]->evaluate(root)); - Value pLength(vpOperand[2]->evaluate(root)); +Value ExpressionSubstrBytes::evaluate(const Document& root, Variables* variables) const { + Value pString(vpOperand[0]->evaluate(root, variables)); + Value pLower(vpOperand[1]->evaluate(root, variables)); + Value pLength(vpOperand[2]->evaluate(root, variables)); string str = pString.coerceToString(); uassert(16034, @@ -3950,10 +3975,10 @@ const char* ExpressionSubstrBytes::getOpName() const { /* ----------------------- ExpressionSubstrCP ---------------------------- */ -Value ExpressionSubstrCP::evaluate(const Document& root) const { - Value inputVal(vpOperand[0]->evaluate(root)); - Value lowerVal(vpOperand[1]->evaluate(root)); - Value lengthVal(vpOperand[2]->evaluate(root)); +Value ExpressionSubstrCP::evaluate(const Document& root, Variables* variables) const { + Value inputVal(vpOperand[0]->evaluate(root, variables)); + Value lowerVal(vpOperand[1]->evaluate(root, variables)); + Value lengthVal(vpOperand[2]->evaluate(root, variables)); std::string str = inputVal.coerceToString(); uassert(34450, @@ -4025,8 +4050,8 @@ const char* ExpressionSubstrCP::getOpName() const { /* ----------------------- ExpressionStrLenBytes ------------------------- */ -Value ExpressionStrLenBytes::evaluate(const Document& root) const { - Value str(vpOperand[0]->evaluate(root)); +Value ExpressionStrLenBytes::evaluate(const Document& root, Variables* variables) const { + Value str(vpOperand[0]->evaluate(root, variables)); uassert(34473, str::stream() << "$strLenBytes requires a string argument, found: " @@ -4048,8 +4073,8 @@ const char* ExpressionStrLenBytes::getOpName() const { /* ----------------------- ExpressionStrLenCP ------------------------- */ -Value ExpressionStrLenCP::evaluate(const Document& root) const { - Value val(vpOperand[0]->evaluate(root)); +Value ExpressionStrLenCP::evaluate(const Document& root, Variables* variables) const { + Value val(vpOperand[0]->evaluate(root, variables)); uassert(34471, str::stream() << "$strLenCP requires a string argument, found: " @@ -4073,9 +4098,9 @@ const char* ExpressionStrLenCP::getOpName() const { /* ----------------------- ExpressionSubtract ---------------------------- */ -Value ExpressionSubtract::evaluate(const Document& root) const { - Value lhs = vpOperand[0]->evaluate(root); - Value rhs = vpOperand[1]->evaluate(root); +Value ExpressionSubtract::evaluate(const Document& root, Variables* variables) const { + Value lhs = vpOperand[0]->evaluate(root, variables); + Value rhs = vpOperand[1]->evaluate(root, variables); BSONType diffType = Value::getWidestNumeric(rhs.getType(), lhs.getType()); @@ -4123,12 +4148,12 @@ const char* ExpressionSubtract::getOpName() const { REGISTER_EXPRESSION(switch, ExpressionSwitch::parse); -Value ExpressionSwitch::evaluate(const Document& root) const { +Value ExpressionSwitch::evaluate(const Document& root, Variables* variables) const { for (auto&& branch : _branches) { - Value caseExpression(branch.first->evaluate(root)); + Value caseExpression(branch.first->evaluate(root, variables)); if (caseExpression.coerceToBool()) { - return branch.second->evaluate(root); + return branch.second->evaluate(root, variables); } } @@ -4136,7 +4161,7 @@ Value ExpressionSwitch::evaluate(const Document& root) const { "$switch could not find a matching branch for an input, and no default was specified.", _default); - return _default->evaluate(root); + return _default->evaluate(root, variables); } boost::intrusive_ptr<Expression> ExpressionSwitch::parse( @@ -4250,8 +4275,8 @@ Value ExpressionSwitch::serialize(bool explain) const { /* ------------------------- ExpressionToLower ----------------------------- */ -Value ExpressionToLower::evaluate(const Document& root) const { - Value pString(vpOperand[0]->evaluate(root)); +Value ExpressionToLower::evaluate(const Document& root, Variables* variables) const { + Value pString(vpOperand[0]->evaluate(root, variables)); string str = pString.coerceToString(); boost::to_lower(str); return Value(str); @@ -4264,8 +4289,8 @@ const char* ExpressionToLower::getOpName() const { /* ------------------------- ExpressionToUpper -------------------------- */ -Value ExpressionToUpper::evaluate(const Document& root) const { - Value pString(vpOperand[0]->evaluate(root)); +Value ExpressionToUpper::evaluate(const Document& root, Variables* variables) const { + Value pString(vpOperand[0]->evaluate(root, variables)); string str(pString.coerceToString()); boost::to_upper(str); return Value(str); @@ -4280,6 +4305,7 @@ const char* ExpressionToUpper::getOpName() const { Value ExpressionTrunc::evaluateNumericArg(const Value& numericArg) const { // There's no point in truncating integers or longs, it will have no effect. + switch (numericArg.getType()) { case NumberDecimal: return Value(numericArg.getDecimal().quantize(Decimal128::kNormalizedZero, @@ -4298,8 +4324,8 @@ const char* ExpressionTrunc::getOpName() const { /* ------------------------- ExpressionType ----------------------------- */ -Value ExpressionType::evaluate(const Document& root) const { - Value val(vpOperand[0]->evaluate(root)); +Value ExpressionType::evaluate(const Document& root, Variables* variables) const { + Value val(vpOperand[0]->evaluate(root, variables)); return Value(StringData(typeName(val.getType()))); } @@ -4363,7 +4389,7 @@ intrusive_ptr<Expression> ExpressionZip::parse( return std::move(newZip); } -Value ExpressionZip::evaluate(const Document& root) const { +Value ExpressionZip::evaluate(const Document& root, Variables* variables) const { // Evaluate input values. vector<vector<Value>> inputValues; inputValues.reserve(_inputs.size()); @@ -4371,7 +4397,7 @@ Value ExpressionZip::evaluate(const Document& root) const { size_t minArraySize = 0; size_t maxArraySize = 0; for (size_t i = 0; i < _inputs.size(); i++) { - Value evalExpr = _inputs[i]->evaluate(root); + Value evalExpr = _inputs[i].get()->evaluate(root, variables); if (evalExpr.nullish()) { return Value(BSONNULL); } @@ -4400,7 +4426,7 @@ Value ExpressionZip::evaluate(const Document& root) const { // If we need default values, evaluate each expression. if (minArraySize != maxArraySize) { for (size_t i = 0; i < _defaults.size(); i++) { - evaluatedDefaults[i] = _defaults[i]->evaluate(root); + evaluatedDefaults[i] = _defaults[i].get()->evaluate(root, variables); } } diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index b1b0cfae250..f7f932cc135 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -126,9 +126,12 @@ public: virtual Value serialize(bool explain) const = 0; /** - * Evaluate expression with respect to the Document given by 'root', and return the result. + * Evaluate the expression with respect to the Document given by 'root' and the Variables given + * by 'variables'. It is an error to supply a Variables argument whose built-in variables (like + * $$NOW) are not set. This method is thread-safe, so long as the 'variables' passed in here is + * not shared between threads. */ - virtual Value evaluate(const Document& root) const = 0; + virtual Value evaluate(const Document& root, Variables* variables) const = 0; /** * Returns information about the paths computed by this expression. This only needs to be @@ -212,6 +215,10 @@ public: */ static void registerExpression(std::string key, Parser parser); + const boost::intrusive_ptr<ExpressionContext>& getExpressionContext() const { + return _expCtx; + } + protected: Expression(const boost::intrusive_ptr<ExpressionContext>& expCtx) : _expCtx(expCtx) { auto varIds = _expCtx->variablesParseState.getDefinedVariableIDs(); @@ -222,10 +229,6 @@ protected: typedef std::vector<boost::intrusive_ptr<Expression>> ExpressionVector; - const boost::intrusive_ptr<ExpressionContext>& getExpressionContext() const { - return _expCtx; - } - virtual void _doAddDependencies(DepsTracker* deps) const = 0; private: @@ -364,13 +367,13 @@ public: explicit ExpressionFromAccumulator(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionFromAccumulator<Accumulator>>(expCtx) {} - Value evaluate(const Document& root) const final { + Value evaluate(const Document& root, Variables* variables) const final { Accumulator accum(this->getExpressionContext()); const size_t n = this->vpOperand.size(); // If a single array arg is given, loop through it passing each member to the accumulator. // If a single, non-array arg is given, pass it directly to the accumulator. if (n == 1) { - Value singleVal = this->vpOperand[0]->evaluate(root); + Value singleVal = this->vpOperand[0]->evaluate(root, variables); if (singleVal.getType() == Array) { for (const Value& val : singleVal.getArray()) { accum.process(val, false); @@ -381,7 +384,7 @@ public: } else { // If multiple arguments are given, pass all arguments to the accumulator. for (auto&& argument : this->vpOperand) { - accum.process(argument->evaluate(root), false); + accum.process(argument->evaluate(root, variables), false); } } return accum.getValue(false); @@ -416,8 +419,8 @@ public: virtual ~ExpressionSingleNumericArg() {} - Value evaluate(const Document& root) const final { - Value arg = this->vpOperand[0]->evaluate(root); + Value evaluate(const Document& root, Variables* variables) const final { + Value arg = this->vpOperand[0]->evaluate(root, variables); if (arg.nullish()) return Value(BSONNULL); @@ -438,7 +441,7 @@ public: class ExpressionConstant final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; Value serialize(bool explain) const final; const char* getOpName() const; @@ -498,8 +501,8 @@ class DateExpressionAcceptingTimeZone : public Expression { public: virtual ~DateExpressionAcceptingTimeZone() {} - Value evaluate(const Document& root) const final { - auto dateVal = _date->evaluate(root); + Value evaluate(const Document& root, Variables* variables) const final { + auto dateVal = _date->evaluate(root, variables); if (dateVal.nullish()) { return Value(BSONNULL); } @@ -508,7 +511,7 @@ public: if (!_timeZone) { return evaluateDate(date, TimeZoneDatabase::utcZone()); } - auto timeZoneId = _timeZone->evaluate(root); + auto timeZoneId = _timeZone->evaluate(root, variables); if (timeZoneId.nullish()) { return Value(BSONNULL); } @@ -546,7 +549,8 @@ public: } if (ExpressionConstant::allNullOrConstant({_date, _timeZone})) { // Everything is a constant, so we can turn into a constant. - return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + return ExpressionConstant::create( + getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables))); } return this; } @@ -650,7 +654,7 @@ public: explicit ExpressionAdd(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionAdd>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -668,7 +672,7 @@ public: explicit ExpressionAllElementsTrue(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionAllElementsTrue, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -679,7 +683,7 @@ public: : ExpressionVariadic<ExpressionAnd>(expCtx) {} boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -697,7 +701,7 @@ public: explicit ExpressionAnyElementTrue(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionAnyElementTrue, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -707,7 +711,7 @@ public: explicit ExpressionArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionArray>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; Value serialize(bool explain) const final; const char* getOpName() const final; }; @@ -718,7 +722,7 @@ public: explicit ExpressionArrayElemAt(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionArrayElemAt, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -727,7 +731,7 @@ public: explicit ExpressionObjectToArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionObjectToArray, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -736,7 +740,7 @@ public: explicit ExpressionArrayToObject(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionArrayToObject, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -753,7 +757,7 @@ public: class ExpressionCoerceToBool final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; Value serialize(bool explain) const final; static boost::intrusive_ptr<ExpressionCoerceToBool> create( @@ -790,7 +794,7 @@ public: ExpressionCompare(const boost::intrusive_ptr<ExpressionContext>& expCtx, CmpOp cmpOp) : ExpressionFixedArity<ExpressionCompare, 2>(expCtx), cmpOp(cmpOp) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; CmpOp getOp() const { @@ -819,7 +823,7 @@ public: explicit ExpressionConcat(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionConcat>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -833,7 +837,7 @@ public: explicit ExpressionConcatArrays(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionConcatArrays>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -846,7 +850,7 @@ class ExpressionCond final : public ExpressionFixedArity<ExpressionCond, 3> { public: explicit ExpressionCond(const boost::intrusive_ptr<ExpressionContext>& expCtx) : Base(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; static boost::intrusive_ptr<Expression> parse( @@ -862,7 +866,7 @@ class ExpressionDateFromString final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document&) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -885,7 +889,7 @@ class ExpressionDateFromParts final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -919,7 +923,8 @@ private: int defaultValue, int minValue, int maxValue, - int* returnValue) const; + int* returnValue, + Variables* variables) const; boost::intrusive_ptr<Expression> _year; boost::intrusive_ptr<Expression> _month; @@ -938,7 +943,7 @@ class ExpressionDateToParts final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -957,7 +962,7 @@ private: boost::intrusive_ptr<Expression> timeZone, boost::intrusive_ptr<Expression> iso8601); - boost::optional<int> evaluateIso8601Flag(const Document& root) const; + boost::optional<int> evaluateIso8601Flag(const Document& root, Variables* variables) const; boost::intrusive_ptr<Expression> _date; boost::intrusive_ptr<Expression> _timeZone; @@ -968,7 +973,7 @@ class ExpressionDateToString final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1027,7 +1032,7 @@ public: explicit ExpressionDivide(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionDivide, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1049,7 +1054,7 @@ public: } boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; Value serialize(bool explain) const final; /* @@ -1116,7 +1121,7 @@ class ExpressionFilter final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1170,7 +1175,7 @@ public: explicit ExpressionIfNull(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionIfNull, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1180,7 +1185,7 @@ public: explicit ExpressionIn(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionIn, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1190,7 +1195,7 @@ public: explicit ExpressionIndexOfArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionIndexOfArray, 2, 4>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1200,7 +1205,7 @@ public: explicit ExpressionIndexOfBytes(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionIndexOfBytes, 2, 4>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1213,7 +1218,7 @@ public: explicit ExpressionIndexOfCP(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionIndexOfCP, 2, 4>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1222,7 +1227,7 @@ class ExpressionLet final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1266,7 +1271,7 @@ public: explicit ExpressionLog(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionLog, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1283,7 +1288,7 @@ class ExpressionMap final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1313,7 +1318,7 @@ private: class ExpressionMeta final : public Expression { public: Value serialize(bool explain) const final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1361,7 +1366,7 @@ public: explicit ExpressionMod(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionMod, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1371,7 +1376,7 @@ public: explicit ExpressionMultiply(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionMultiply>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -1400,7 +1405,7 @@ public: explicit ExpressionNot(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionNot, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1416,7 +1421,7 @@ public: class ExpressionObject final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; Value serialize(bool explain) const final; static boost::intrusive_ptr<ExpressionObject> create( @@ -1462,7 +1467,7 @@ public: : ExpressionVariadic<ExpressionOr>(expCtx) {} boost::intrusive_ptr<Expression> optimize() final; - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -1483,7 +1488,7 @@ public: const boost::intrusive_ptr<ExpressionContext>& expCtx, Value base, Value exp); private: - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1493,7 +1498,7 @@ public: explicit ExpressionRange(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionRange, 2, 3>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1503,7 +1508,7 @@ public: explicit ExpressionReduce(const boost::intrusive_ptr<ExpressionContext>& expCtx) : Expression(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; boost::intrusive_ptr<Expression> optimize() final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1540,7 +1545,7 @@ public: explicit ExpressionSetDifference(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSetDifference, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1550,7 +1555,7 @@ public: explicit ExpressionSetEquals(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionSetEquals>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; void validateArguments(const ExpressionVector& args) const final; }; @@ -1561,7 +1566,7 @@ public: explicit ExpressionSetIntersection(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionSetIntersection>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -1581,7 +1586,7 @@ public: : ExpressionFixedArity<ExpressionSetIsSubset, 2>(expCtx) {} boost::intrusive_ptr<Expression> optimize() override; - Value evaluate(const Document& root) const override; + Value evaluate(const Document& root, Variables* variables) const override; const char* getOpName() const final; private: @@ -1594,7 +1599,7 @@ public: explicit ExpressionSetUnion(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionVariadic<ExpressionSetUnion>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; bool isAssociative() const final { @@ -1612,7 +1617,7 @@ public: explicit ExpressionSize(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSize, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1622,7 +1627,7 @@ public: explicit ExpressionReverseArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionReverseArray, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1632,7 +1637,7 @@ public: explicit ExpressionSlice(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionRangedArity<ExpressionSlice, 2, 3>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1642,17 +1647,16 @@ public: explicit ExpressionIsArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionIsArray, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; - class ExpressionSplit final : public ExpressionFixedArity<ExpressionSplit, 2> { public: explicit ExpressionSplit(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSplit, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1672,7 +1676,7 @@ public: explicit ExpressionStrcasecmp(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionStrcasecmp, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1682,7 +1686,7 @@ public: explicit ExpressionSubstrBytes(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSubstrBytes, 3>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const; }; @@ -1692,7 +1696,7 @@ public: explicit ExpressionSubstrCP(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSubstrCP, 3>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1702,7 +1706,7 @@ public: explicit ExpressionStrLenBytes(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionStrLenBytes, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1712,7 +1716,7 @@ public: explicit ExpressionStrLenCP(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionStrLenCP, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1722,7 +1726,7 @@ public: explicit ExpressionSubtract(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionSubtract, 2>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1732,7 +1736,7 @@ public: explicit ExpressionSwitch(const boost::intrusive_ptr<ExpressionContext>& expCtx) : Expression(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; boost::intrusive_ptr<Expression> optimize() final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, @@ -1757,7 +1761,7 @@ public: explicit ExpressionToLower(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionToLower, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1767,11 +1771,10 @@ public: explicit ExpressionToUpper(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionToUpper, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; - class ExpressionTrunc final : public ExpressionSingleNumericArg<ExpressionTrunc> { public: explicit ExpressionTrunc(const boost::intrusive_ptr<ExpressionContext>& expCtx) @@ -1787,7 +1790,7 @@ public: explicit ExpressionType(const boost::intrusive_ptr<ExpressionContext>& expCtx) : ExpressionFixedArity<ExpressionType, 1>(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const final; }; @@ -1853,7 +1856,7 @@ public: explicit ExpressionZip(const boost::intrusive_ptr<ExpressionContext>& expCtx) : Expression(expCtx) {} - Value evaluate(const Document& root) const final; + Value evaluate(const Document& root, Variables* variables) const final; boost::intrusive_ptr<Expression> optimize() final; static boost::intrusive_ptr<Expression> parse( const boost::intrusive_ptr<ExpressionContext>& expCtx, diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index 532e298c1c1..e18d7d2f5cf 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -66,7 +66,7 @@ static Value evaluateExpression(const string& expressionName, VariablesParseState vps = expCtx->variablesParseState; const BSONObj obj = BSON(expressionName << ImplicitValue::convertToValue(operands)); auto expression = Expression::parseExpression(expCtx, obj, vps); - Value result = expression->evaluate(Document()); + Value result = expression->evaluate({}, &expCtx->variables); return result; } @@ -163,8 +163,9 @@ class ExpressionNaryTestOneArg : public ExpressionBaseTest { public: virtual void assertEvaluates(Value input, Value output) { addOperand(_expr, input); - ASSERT_VALUE_EQ(output, _expr->evaluate(Document())); - ASSERT_EQUALS(output.getType(), _expr->evaluate(Document()).getType()); + ASSERT_VALUE_EQ(output, _expr->evaluate({}, &_expr->getExpressionContext()->variables)); + ASSERT_EQUALS(output.getType(), + _expr->evaluate({}, &_expr->getExpressionContext()->variables).getType()); } intrusive_ptr<ExpressionNary> _expr; @@ -175,13 +176,13 @@ public: /** A dummy child of ExpressionNary used for testing. */ class Testable : public ExpressionNary { public: - virtual Value evaluate(const Document& root) const { + virtual Value evaluate(const Document& root, Variables* variables) const { // Just put all the values in a list. // By default, this is not associative/commutative so the results will change if // instantiated as commutative or associative and operations are reordered. vector<Value> values; for (ExpressionVector::const_iterator i = vpOperand.begin(); i != vpOperand.end(); ++i) { - values.push_back((*i)->evaluate(root)); + values.push_back((*i)->evaluate(root, variables)); } return Value(values); } @@ -1075,7 +1076,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<ExpressionNary> expression = new ExpressionAdd(expCtx); populateOperands(expression); - ASSERT_BSONOBJ_EQ(expectedResult(), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(expectedResult(), toBson(expression->evaluate({}, &expCtx->variables))); } protected: @@ -1091,7 +1092,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<ExpressionNary> expression = new ExpressionAdd(expCtx); expression->addOperand(ExpressionConstant::create(expCtx, Value(2))); - ASSERT_BSONOBJ_EQ(BSON("" << 2), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(BSON("" << 2), toBson(expression->evaluate({}, &expCtx->variables))); } }; @@ -1110,7 +1111,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<ExpressionNary> expression = new ExpressionAdd(expCtx); expression->addOperand(ExpressionConstant::create(expCtx, Value("a"_sd))); - ASSERT_THROWS(expression->evaluate(Document()), AssertionException); + ASSERT_THROWS(expression->evaluate({}, &expCtx->variables), AssertionException); } }; @@ -1121,7 +1122,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<ExpressionNary> expression = new ExpressionAdd(expCtx); expression->addOperand(ExpressionConstant::create(expCtx, Value(true))); - ASSERT_THROWS(expression->evaluate(Document()), AssertionException); + ASSERT_THROWS(expression->evaluate({}, &expCtx->variables), AssertionException); } }; @@ -1360,11 +1361,13 @@ public: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), - toBson(expression->evaluate(fromBson(BSON("a" << 1))))); + ASSERT_BSONOBJ_EQ( + BSON("" << expectedResult()), + toBson(expression->evaluate(fromBson(BSON("a" << 1)), &expCtx->variables))); intrusive_ptr<Expression> optimized = expression->optimize(); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), - toBson(optimized->evaluate(fromBson(BSON("a" << 1))))); + ASSERT_BSONOBJ_EQ( + BSON("" << expectedResult()), + toBson(optimized->evaluate(fromBson(BSON("a" << 1)), &expCtx->variables))); } protected: @@ -1655,7 +1658,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> nested = ExpressionConstant::create(expCtx, Value(5)); intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(expCtx, nested); - ASSERT(expression->evaluate(Document()).getBool()); + ASSERT(expression->evaluate({}, &expCtx->variables).getBool()); } }; @@ -1666,7 +1669,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> nested = ExpressionConstant::create(expCtx, Value(0)); intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(expCtx, nested); - ASSERT(!expression->evaluate(Document()).getBool()); + ASSERT(!expression->evaluate({}, &expCtx->variables).getBool()); } }; @@ -1774,10 +1777,10 @@ public: // Check expression spec round trip. ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); // Check evaluation result. - ASSERT_BSONOBJ_EQ(expectedResult(), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(expectedResult(), toBson(expression->evaluate({}, &expCtx->variables))); // Check that the result is the same after optimizing. intrusive_ptr<Expression> optimized = expression->optimize(); - ASSERT_BSONOBJ_EQ(expectedResult(), toBson(optimized->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(expectedResult(), toBson(optimized->evaluate({}, &expCtx->variables))); } protected: @@ -2014,7 +2017,7 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); - ASSERT_VALUE_EQ(expression->evaluate(Document()), Value(true)); + ASSERT_VALUE_EQ(expression->evaluate({}, &expCtx->variables), Value(true)); } }; @@ -2147,7 +2150,7 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionConstant::create(expCtx, Value(5)); - assertBinaryEqual(BSON("" << 5), toBson(expression->evaluate(Document()))); + assertBinaryEqual(BSON("" << 5), toBson(expression->evaluate({}, &expCtx->variables))); } }; @@ -2163,7 +2166,7 @@ public: intrusive_ptr<Expression> expression = ExpressionConstant::parse(expCtx, specElement, vps); assertBinaryEqual(BSON("" << "foo"), - toBson(expression->evaluate(Document()))); + toBson(expression->evaluate({}, &expCtx->variables))); } }; @@ -2229,7 +2232,9 @@ private: TEST(ExpressionConstantTest, ConstantOfValueMissingRemovesField) { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionConstant::create(expCtx, Value()); - assertBinaryEqual(BSONObj(), toBson(expression->evaluate(Document{{"foo", Value("bar"_sd)}}))); + assertBinaryEqual( + BSONObj(), + toBson(expression->evaluate(Document{{"foo", Value("bar"_sd)}}, &expCtx->variables))); } TEST(ExpressionConstantTest, ConstantOfValueMissingSerializesToRemoveSystemVar) { @@ -2332,11 +2337,11 @@ TEST(ExpressionPowTest, ThrowsWhenBaseZeroAndExpNegative) { VariablesParseState vps = expCtx->variablesParseState; const auto expr = Expression::parseExpression(expCtx, BSON("$pow" << BSON_ARRAY(0 << -5)), vps); - ASSERT_THROWS([&] { expr->evaluate(Document()); }(), AssertionException); + ASSERT_THROWS([&] { expr->evaluate({}, &expCtx->variables); }(), AssertionException); const auto exprWithLong = Expression::parseExpression(expCtx, BSON("$pow" << BSON_ARRAY(0LL << -5LL)), vps); - ASSERT_THROWS([&] { expr->evaluate(Document()); }(), AssertionException); + ASSERT_THROWS([&] { expr->evaluate({}, &expCtx->variables); }(), AssertionException); } TEST(ExpressionPowTest, LargeExponentValuesWithBaseOfOne) { @@ -2434,7 +2439,9 @@ TEST(FieldPath, RemoveOptimizesToMissingValue) { auto optimizedExpr = expression->optimize(); - ASSERT_VALUE_EQ(Value(), optimizedExpr->evaluate(Document(BSON("x" << BSON("y" << 123))))); + ASSERT_VALUE_EQ( + Value(), + optimizedExpr->evaluate(Document(BSON("x" << BSON("y" << 123))), &expCtx->variables)); } TEST(FieldPath, NoOptimizationOnNormalPath) { @@ -2556,7 +2563,7 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a"); - assertBinaryEqual(fromjson("{}"), toBson(expression->evaluate(Document()))); + assertBinaryEqual(fromjson("{}"), toBson(expression->evaluate({}, &expCtx->variables))); } }; @@ -2566,8 +2573,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a"); - assertBinaryEqual(fromjson("{'':123}"), - toBson(expression->evaluate(fromBson(BSON("a" << 123))))); + assertBinaryEqual( + fromjson("{'':123}"), + toBson(expression->evaluate(fromBson(BSON("a" << 123)), &expCtx->variables))); } }; @@ -2577,8 +2585,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{}"), - toBson(expression->evaluate(fromBson(fromjson("{a:null}"))))); + assertBinaryEqual( + fromjson("{}"), + toBson(expression->evaluate(fromBson(fromjson("{a:null}")), &expCtx->variables))); } }; @@ -2588,8 +2597,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{}"), - toBson(expression->evaluate(fromBson(fromjson("{a:undefined}"))))); + assertBinaryEqual( + fromjson("{}"), + toBson(expression->evaluate(fromBson(fromjson("{a:undefined}")), &expCtx->variables))); } }; @@ -2599,8 +2609,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{}"), - toBson(expression->evaluate(fromBson(fromjson("{z:1}"))))); + assertBinaryEqual( + fromjson("{}"), + toBson(expression->evaluate(fromBson(fromjson("{z:1}")), &expCtx->variables))); } }; @@ -2610,7 +2621,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{}"), toBson(expression->evaluate(fromBson(BSON("a" << 2))))); + assertBinaryEqual( + fromjson("{}"), + toBson(expression->evaluate(fromBson(BSON("a" << 2)), &expCtx->variables))); } }; @@ -2621,7 +2634,8 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); assertBinaryEqual(BSON("" << 55), - toBson(expression->evaluate(fromBson(BSON("a" << BSON("b" << 55)))))); + toBson(expression->evaluate(fromBson(BSON("a" << BSON("b" << 55))), + &expCtx->variables))); } }; @@ -2631,8 +2645,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{}"), - toBson(expression->evaluate(fromBson(BSON("a" << BSONObj()))))); + assertBinaryEqual( + fromjson("{}"), + toBson(expression->evaluate(fromBson(BSON("a" << BSONObj())), &expCtx->variables))); } }; @@ -2642,8 +2657,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(BSON("" << BSONArray()), - toBson(expression->evaluate(fromBson(BSON("a" << BSONArray()))))); + assertBinaryEqual( + BSON("" << BSONArray()), + toBson(expression->evaluate(fromBson(BSON("a" << BSONArray())), &expCtx->variables))); } }; @@ -2653,8 +2669,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{'':[]}"), - toBson(expression->evaluate(fromBson(fromjson("{a:[null]}"))))); + assertBinaryEqual( + fromjson("{'':[]}"), + toBson(expression->evaluate(fromBson(fromjson("{a:[null]}")), &expCtx->variables))); } }; @@ -2665,7 +2682,8 @@ public: intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); assertBinaryEqual(fromjson("{'':[]}"), - toBson(expression->evaluate(fromBson(fromjson("{a:[undefined]}"))))); + toBson(expression->evaluate(fromBson(fromjson("{a:[undefined]}")), + &expCtx->variables))); } }; @@ -2675,8 +2693,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{'':[]}"), - toBson(expression->evaluate(fromBson(fromjson("{a:[1]}"))))); + assertBinaryEqual( + fromjson("{'':[]}"), + toBson(expression->evaluate(fromBson(fromjson("{a:[1]}")), &expCtx->variables))); } }; @@ -2686,8 +2705,9 @@ public: void run() { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); - assertBinaryEqual(fromjson("{'':[9]}"), - toBson(expression->evaluate(fromBson(fromjson("{a:[{b:9}]}"))))); + assertBinaryEqual( + fromjson("{'':[9]}"), + toBson(expression->evaluate(fromBson(fromjson("{a:[{b:9}]}")), &expCtx->variables))); } }; @@ -2699,7 +2719,8 @@ public: intrusive_ptr<Expression> expression = ExpressionFieldPath::create(expCtx, "a.b"); assertBinaryEqual(fromjson("{'':[9,20]}"), toBson(expression->evaluate( - fromBson(fromjson("{a:[{b:9},null,undefined,{g:4},{b:20},{}]}"))))); + fromBson(fromjson("{a:[{b:9},null,undefined,{g:4},{b:20},{}]}")), + &expCtx->variables))); } }; @@ -2714,7 +2735,8 @@ public: "{b:{c:3}}," "{b:[{c:4}]}," "{b:[{c:[5]}]}," - "{b:{c:[6,7]}}]}"))))); + "{b:{c:[6,7]}}]}")), + &expCtx->variables))); } }; @@ -2884,20 +2906,24 @@ TEST(ParseObject, ShouldRejectExpressionAsTheSecondField) { TEST(ExpressionObjectEvaluate, EmptyObjectShouldEvaluateToEmptyDocument) { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto object = ExpressionObject::create(expCtx, {}); - ASSERT_VALUE_EQ(Value(Document()), object->evaluate(Document())); - ASSERT_VALUE_EQ(Value(Document()), object->evaluate(Document{{"a", 1}})); - ASSERT_VALUE_EQ(Value(Document()), object->evaluate(Document{{"_id", "ID"_sd}})); + ASSERT_VALUE_EQ(Value(Document()), object->evaluate(Document(), &(expCtx->variables))); + ASSERT_VALUE_EQ(Value(Document()), object->evaluate(Document{{"a", 1}}, &(expCtx->variables))); + ASSERT_VALUE_EQ(Value(Document()), + object->evaluate(Document{{"_id", "ID"_sd}}, &(expCtx->variables))); } TEST(ExpressionObjectEvaluate, ShouldEvaluateEachField) { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto object = ExpressionObject::create(expCtx, {{"a", makeConstant(1)}, {"b", makeConstant(5)}}); - ASSERT_VALUE_EQ(Value(Document{{"a", 1}, {"b", 5}}), object->evaluate(Document())); - ASSERT_VALUE_EQ(Value(Document{{"a", 1}, {"b", 5}}), object->evaluate(Document{{"a", 1}})); ASSERT_VALUE_EQ(Value(Document{{"a", 1}, {"b", 5}}), - object->evaluate(Document{{"_id", "ID"_sd}})); + object->evaluate(Document(), &(expCtx->variables))); + ASSERT_VALUE_EQ(Value(Document{{"a", 1}, {"b", 5}}), + object->evaluate(Document{{"a", 1}}, &(expCtx->variables))); + ASSERT_VALUE_EQ(Value(Document{{"a", 1}, {"b", 5}}), + object->evaluate(Document{{"_id", "ID"_sd}}, &(expCtx->variables))); } TEST(ExpressionObjectEvaluate, OrderOfFieldsInOutputShouldMatchOrderInSpecification) { @@ -2908,7 +2934,8 @@ TEST(ExpressionObjectEvaluate, OrderOfFieldsInOutputShouldMatchOrderInSpecificat {"c", ExpressionFieldPath::create(expCtx, "c")}}); ASSERT_VALUE_EQ( Value(Document{{"a", "A"_sd}, {"b", "B"_sd}, {"c", "C"_sd}}), - object->evaluate(Document{{"c", "C"_sd}, {"a", "A"_sd}, {"b", "B"_sd}, {"_id", "ID"_sd}})); + object->evaluate(Document{{"c", "C"_sd}, {"a", "A"_sd}, {"b", "B"_sd}, {"_id", "ID"_sd}}, + &(expCtx->variables))); } TEST(ExpressionObjectEvaluate, ShouldRemoveFieldsThatHaveMissingValues) { @@ -2916,8 +2943,8 @@ TEST(ExpressionObjectEvaluate, ShouldRemoveFieldsThatHaveMissingValues) { auto object = ExpressionObject::create(expCtx, {{"a", ExpressionFieldPath::create(expCtx, "a.b")}, {"b", ExpressionFieldPath::create(expCtx, "missing")}}); - ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document())); - ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document{{"a", 1}})); + ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document(), &(expCtx->variables))); + ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document{{"a", 1}}, &(expCtx->variables))); } TEST(ExpressionObjectEvaluate, ShouldEvaluateFieldsWithinNestedObject) { @@ -2928,20 +2955,21 @@ TEST(ExpressionObjectEvaluate, ShouldEvaluateFieldsWithinNestedObject) { ExpressionObject::create( expCtx, {{"b", makeConstant(1)}, {"c", ExpressionFieldPath::create(expCtx, "_id")}})}}); - ASSERT_VALUE_EQ(Value(Document{{"a", Document{{"b", 1}}}}), object->evaluate(Document())); + ASSERT_VALUE_EQ(Value(Document{{"a", Document{{"b", 1}}}}), + object->evaluate(Document(), &(expCtx->variables))); ASSERT_VALUE_EQ(Value(Document{{"a", Document{{"b", 1}, {"c", "ID"_sd}}}}), - object->evaluate(Document{{"_id", "ID"_sd}})); + object->evaluate(Document{{"_id", "ID"_sd}}, &(expCtx->variables))); } TEST(ExpressionObjectEvaluate, ShouldEvaluateToEmptyDocumentIfAllFieldsAreMissing) { intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); auto object = ExpressionObject::create(expCtx, {{"a", ExpressionFieldPath::create(expCtx, "missing")}}); - ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document())); + ASSERT_VALUE_EQ(Value(Document{}), object->evaluate(Document(), &(expCtx->variables))); auto objectWithNestedObject = ExpressionObject::create(expCtx, {{"nested", object}}); ASSERT_VALUE_EQ(Value(Document{{"nested", Document{}}}), - objectWithNestedObject->evaluate(Document())); + objectWithNestedObject->evaluate(Document(), &(expCtx->variables))); } // @@ -3050,7 +3078,7 @@ TEST(ExpressionObjectOptimizations, OptimizingAnObjectShouldOptimizeSubExpressio auto expConstant = dynamic_cast<ExpressionConstant*>(optimizedObject->getChildExpressions()[0].second.get()); ASSERT_TRUE(expConstant); - ASSERT_VALUE_EQ(expConstant->evaluate(Document()), Value(3)); + ASSERT_VALUE_EQ(expConstant->evaluate(Document(), &(expCtx->variables)), Value(3)); }; } // namespace Object @@ -3067,11 +3095,13 @@ public: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), - toBson(expression->evaluate(fromBson(BSON("a" << 1))))); + ASSERT_BSONOBJ_EQ( + BSON("" << expectedResult()), + toBson(expression->evaluate(fromBson(BSON("a" << 1)), &expCtx->variables))); intrusive_ptr<Expression> optimized = expression->optimize(); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), - toBson(optimized->evaluate(fromBson(BSON("a" << 1))))); + ASSERT_BSONOBJ_EQ( + BSON("" << expectedResult()), + toBson(optimized->evaluate(fromBson(BSON("a" << 1)), &expCtx->variables))); } protected: @@ -3579,7 +3609,7 @@ public: VariablesParseState vps = expCtx->variablesParseState; const intrusive_ptr<Expression> expr = Expression::parseExpression(expCtx, obj, vps); - Value result = expr->evaluate(Document()); + Value result = expr->evaluate({}, &expCtx->variables); if (result.getType() == Array) { result = sortSet(result); } @@ -3606,7 +3636,7 @@ public: // same const intrusive_ptr<Expression> expr = Expression::parseExpression(expCtx, obj, vps); - expr->evaluate(Document()); + expr->evaluate(Document(), &expCtx->variables); }, AssertionException); } @@ -3888,7 +3918,8 @@ private: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(BSON("" << expectedResult), + toBson(expression->evaluate({}, &expCtx->variables))); } }; @@ -4014,7 +4045,8 @@ public: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), + toBson(expression->evaluate({}, &expCtx->variables))); } protected: @@ -4120,7 +4152,7 @@ TEST(ExpressionSubstrCPTest, DoesThrowWithBadContinuationByte) { const auto continuationByte = "\x80\x00"_sd; const auto expr = Expression::parseExpression( expCtx, BSON("$substrCP" << BSON_ARRAY(continuationByte << 0 << 1)), vps); - ASSERT_THROWS({ expr->evaluate(Document()); }, AssertionException); + ASSERT_THROWS({ expr->evaluate(Document(), &expCtx->variables); }, AssertionException); } TEST(ExpressionSubstrCPTest, DoesThrowWithInvalidLeadingByte) { @@ -4130,7 +4162,7 @@ TEST(ExpressionSubstrCPTest, DoesThrowWithInvalidLeadingByte) { const auto leadingByte = "\xFF\x00"_sd; const auto expr = Expression::parseExpression( expCtx, BSON("$substrCP" << BSON_ARRAY(leadingByte << 0 << 1)), vps); - ASSERT_THROWS({ expr->evaluate(Document()); }, AssertionException); + ASSERT_THROWS({ expr->evaluate(Document(), &expCtx->variables); }, AssertionException); } TEST(ExpressionSubstrCPTest, WithStandardValue) { @@ -4404,7 +4436,8 @@ public: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), + toBson(expression->evaluate({}, &expCtx->variables))); } protected: @@ -4461,7 +4494,8 @@ public: VariablesParseState vps = expCtx->variablesParseState; intrusive_ptr<Expression> expression = Expression::parseOperand(expCtx, specElement, vps); ASSERT_BSONOBJ_EQ(constify(spec()), expressionToBson(expression)); - ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), toBson(expression->evaluate(Document()))); + ASSERT_BSONOBJ_EQ(BSON("" << expectedResult()), + toBson(expression->evaluate({}, &expCtx->variables))); } protected: @@ -4523,7 +4557,7 @@ public: VariablesParseState vps = expCtx->variablesParseState; const intrusive_ptr<Expression> expr = Expression::parseExpression(expCtx, obj, vps); - const Value result = expr->evaluate(Document()); + const Value result = expr->evaluate({}, &expCtx->variables); if (ValueComparator().evaluate(result != expected)) { string errMsg = str::stream() << "for expression " << field.first.toString() << " with argument " @@ -4547,7 +4581,7 @@ public: // same const intrusive_ptr<Expression> expr = Expression::parseExpression(expCtx, obj, vps); - expr->evaluate(Document()); + expr->evaluate(Document(), &expCtx->variables); }, AssertionException); } @@ -5073,14 +5107,16 @@ TEST_F(DateExpressionTest, RejectsArraysWithinObjectSpecification) { // It will parse as an ExpressionArray, and fail at runtime. auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"_id", 0}}; - ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), AssertionException, 16006); + ASSERT_THROWS_CODE( + dateExp->evaluate(contextDoc, &expCtx->variables), AssertionException, 16006); // Test that it rejects an array for the timezone option. spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << BSON_ARRAY("Europe/London"))); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); contextDoc = Document{{"_id", 0}}; - ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), AssertionException, 40533); + ASSERT_THROWS_CODE( + dateExp->evaluate(contextDoc, &expCtx->variables), AssertionException, 40533); } } @@ -5090,7 +5126,8 @@ TEST_F(DateExpressionTest, RejectsTypesThatCannotCoerceToDate) { BSONObj spec = BSON(expName << "$stringField"); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"stringField", "string"_sd}}; - ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), AssertionException, 16006); + ASSERT_THROWS_CODE( + dateExp->evaluate(contextDoc, &expCtx->variables), AssertionException, 16006); } } @@ -5100,7 +5137,7 @@ TEST_F(DateExpressionTest, AcceptsObjectIds) { BSONObj spec = BSON(expName << "$oid"); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"oid", OID::gen()}}; - dateExp->evaluate(contextDoc); // Should not throw. + dateExp->evaluate(contextDoc, &expCtx->variables); // Should not throw. } } @@ -5110,7 +5147,7 @@ TEST_F(DateExpressionTest, AcceptsTimestamps) { BSONObj spec = BSON(expName << "$ts"); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"ts", Timestamp{Date_t{}}}}; - dateExp->evaluate(contextDoc); // Should not throw. + dateExp->evaluate(contextDoc, &expCtx->variables); // Should not throw. } } @@ -5121,7 +5158,8 @@ TEST_F(DateExpressionTest, RejectsNonStringTimezone) { << "$intField")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"intField", 4}}; - ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), AssertionException, 40533); + ASSERT_THROWS_CODE( + dateExp->evaluate(contextDoc, &expCtx->variables), AssertionException, 40533); } } @@ -5132,7 +5170,8 @@ TEST_F(DateExpressionTest, RejectsUnrecognizedTimeZoneSpecification) { << "UNRECOGNIZED!")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"_id", 0}}; - ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), AssertionException, 40485); + ASSERT_THROWS_CODE( + dateExp->evaluate(contextDoc, &expCtx->variables), AssertionException, 40485); } } @@ -5217,7 +5256,7 @@ TEST_F(DateExpressionTest, DoesRespectTimeZone) { << "America/New_York")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto contextDoc = Document{{"_id", 0}}; - dateExp->evaluate(contextDoc); // Should not throw. + dateExp->evaluate(contextDoc, &expCtx->variables); // Should not throw. } // Make sure the time zone is used during evaluation. @@ -5225,14 +5264,14 @@ TEST_F(DateExpressionTest, DoesRespectTimeZone) { auto specWithoutTimezone = BSON("$hour" << BSON("date" << date)); auto hourWithoutTimezone = Expression::parseExpression(expCtx, specWithoutTimezone, expCtx->variablesParseState) - ->evaluate({}); + ->evaluate({}, &expCtx->variables); ASSERT_VALUE_EQ(hourWithoutTimezone, Value(19)); auto specWithTimezone = BSON("$hour" << BSON("date" << date << "timezone" << "America/New_York")); auto hourWithTimezone = Expression::parseExpression(expCtx, specWithTimezone, expCtx->variablesParseState) - ->evaluate({}); + ->evaluate({}, &expCtx->variables); ASSERT_VALUE_EQ(hourWithTimezone, Value(15)); } @@ -5247,30 +5286,30 @@ TEST_F(DateExpressionTest, DoesResultInNullIfGivenNullishInput) { auto spec = BSON(expName << BSON("date" << "$missing")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); spec = BSON(expName << BSON("date" << BSONNULL)); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); spec = BSON(expName << BSON("date" << BSONUndefined)); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); // Test that the expression results in null if the date is present but the timezone is // nullish. spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << "$missing")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << BSONNULL)); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << BSONUndefined)); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); // Test that the expression results in null if the date and timezone both nullish. spec = BSON(expName << BSON("date" @@ -5278,7 +5317,7 @@ TEST_F(DateExpressionTest, DoesResultInNullIfGivenNullishInput) { << "timezone" << BSONUndefined)); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); // Test that the expression results in null if the date is nullish and timezone is present. spec = BSON(expName << BSON("date" @@ -5286,7 +5325,7 @@ TEST_F(DateExpressionTest, DoesResultInNullIfGivenNullishInput) { << "timezone" << "Europe/London")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc)); + ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc, &expCtx->variables)); } } @@ -5421,7 +5460,7 @@ TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant) ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); Date_t dateVal = Date_t::fromMillisSinceEpoch(1499173797000); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); // Test that it becomes a constant with the dateString and timezone being a constant. spec = BSON("$dateFromString" << BSON("dateString" @@ -5432,7 +5471,7 @@ TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant) ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); dateVal = Date_t::fromMillisSinceEpoch(1499170197000); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); // Test that it does *not* become a constant if dateString is not a constant. spec = BSON("$dateFromString" << BSON("dateString" @@ -5455,7 +5494,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsUnparsableString) { auto spec = BSON("$dateFromString" << BSON("dateString" << "60.Monday1770/06:59")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40553); } TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInString) { @@ -5464,12 +5503,12 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInString) { auto spec = BSON("$dateFromString" << BSON("dateString" << "2017-07-13T10:02:57 Europe/London")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40553); spec = BSON("$dateFromString" << BSON("dateString" << "July 4, 2017 Europe/London")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40553); } TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) { @@ -5481,7 +5520,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) { << "timezone" << "Europe/London")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40551); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40551); // Test with timezone abbreviation and timezone spec = BSON("$dateFromString" << BSON("dateString" @@ -5489,7 +5528,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) { << "timezone" << "Europe/London")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40551); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40551); // Test with GMT offset and timezone spec = BSON("$dateFromString" << BSON("dateString" @@ -5497,7 +5536,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) { << "timezone" << "Europe/London")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40554); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40554); // Test with GMT offset and GMT timezone spec = BSON("$dateFromString" << BSON("dateString" @@ -5505,7 +5544,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) { << "timezone" << "GMT")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); - ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40554); + ASSERT_THROWS_CODE(dateExp->evaluate({}, &expCtx->variables), AssertionException, 40554); } TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { @@ -5517,7 +5556,7 @@ TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { << "-01:00")); auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); auto dateVal = Date_t::fromMillisSinceEpoch(1501242472912); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); spec = BSON("$dateFromString" << BSON("dateString" << "2017-07-28T10:47:52.912" @@ -5525,7 +5564,7 @@ TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { << "+01:00")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); dateVal = Date_t::fromMillisSinceEpoch(1501235272912); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); spec = BSON("$dateFromString" << BSON("dateString" << "2017-07-28T10:47:52.912" @@ -5533,7 +5572,7 @@ TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { << "+0445")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); dateVal = Date_t::fromMillisSinceEpoch(1501221772912); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); spec = BSON("$dateFromString" << BSON("dateString" << "2017-07-28T10:47:52.912" @@ -5541,7 +5580,7 @@ TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { << "+10:45")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); dateVal = Date_t::fromMillisSinceEpoch(1501200172912); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); spec = BSON("$dateFromString" << BSON("dateString" << "1945-07-28T10:47:52.912" @@ -5549,7 +5588,7 @@ TEST_F(ExpressionDateFromStringTest, ReadWithUTCOffset) { << "-08:00")); dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); dateVal = Date_t::fromMillisSinceEpoch(-770879527088); - ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}, &expCtx->variables)); } } // namespace ExpressionDateFromStringTest diff --git a/src/mongo/db/pipeline/granularity_rounder_powers_of_two.cpp b/src/mongo/db/pipeline/granularity_rounder_powers_of_two.cpp index 134af30120d..e6e26c367cd 100644 --- a/src/mongo/db/pipeline/granularity_rounder_powers_of_two.cpp +++ b/src/mongo/db/pipeline/granularity_rounder_powers_of_two.cpp @@ -82,7 +82,8 @@ Value GranularityRounderPowersOfTwo::roundUp(Value value) { exp = Value(63 - countLeadingZeros64(number) + 1); } - return ExpressionPow::create(getExpCtx(), Value(2), exp)->evaluate(Document()); + return ExpressionPow::create(getExpCtx(), Value(2), exp) + ->evaluate(Document(), &getExpCtx()->variables); } Value GranularityRounderPowersOfTwo::roundDown(Value value) { @@ -114,7 +115,8 @@ Value GranularityRounderPowersOfTwo::roundDown(Value value) { } } - return ExpressionPow::create(getExpCtx(), Value(2), exp)->evaluate(Document()); + return ExpressionPow::create(getExpCtx(), Value(2), exp) + ->evaluate(Document(), &getExpCtx()->variables); } string GranularityRounderPowersOfTwo::getName() { diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp index d9c8c19373c..cc288fae9ea 100644 --- a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp +++ b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp @@ -152,7 +152,10 @@ void InclusionNode::addComputedFields(MutableDocument* outputDoc, const Document } else { auto expressionIt = _expressions.find(field); invariant(expressionIt != _expressions.end()); - outputDoc->setField(field, expressionIt->second->evaluate(root)); + outputDoc->setField( + field, + expressionIt->second->evaluate( + root, &(expressionIt->second->getExpressionContext()->variables))); } } } |