diff options
-rw-r--r-- | jstests/core/arrayfind10.js | 59 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_filter.cpp | 27 |
2 files changed, 62 insertions, 24 deletions
diff --git a/jstests/core/arrayfind10.js b/jstests/core/arrayfind10.js index 0040dc18099..80652ebca6a 100644 --- a/jstests/core/arrayfind10.js +++ b/jstests/core/arrayfind10.js @@ -1,6 +1,5 @@ /** - * Tests some of the find command's quirky semantics regarding how arrays are handled in various - * situations. Includes tests for bugs described in SERVER-49720. + * Tests some of the find command's semantics with respect to how arrays are handled. */ (function() { "use strict"; @@ -10,18 +9,54 @@ load("jstests/aggregation/extras/utils.js"); // arrayEq const t = db.jstests_arrayfind10; t.drop(); -t.save({a: "foo"}); -t.save({a: ["foo"]}); -t.save({a: [["foo"]]}); -t.save({a: [[["foo"]]]}); +function runWithAndWithoutIndex(keyPattern, testFunc) { + testFunc(); + assert.commandWorked(t.createIndex(keyPattern)); + testFunc(); +} + +assert.commandWorked(t.insert([{a: "foo"}, {a: ["foo"]}, {a: [["foo"]]}, {a: [[["foo"]]]}])); -function doTest1(t) { +runWithAndWithoutIndex({a: 1}, () => { assert(arrayEq(t.find({a: "foo"}, {_id: 0}).toArray(), [{a: "foo"}, {a: ["foo"]}])); -} +}); -doTest1(t); -t.ensureIndex({'a': 1}); -doTest1(t); +assert(t.drop()); -t.drop(); +assert.commandWorked(t.insert([ + {a: [123, "foo"]}, + {a: ["foo", 123]}, + {a: ["bar", "foo"]}, + {a: ["bar", "baz", "foo"]}, + {a: ["bar", "baz", 123]} +])); + +runWithAndWithoutIndex({a: 1}, () => { + assert(arrayEq( + t.find({a: "foo"}, {_id: 0}).toArray(), + [{a: [123, "foo"]}, {a: ["foo", 123]}, {a: ["bar", "foo"]}, {a: ["bar", "baz", "foo"]}])); +}); + +assert(t.drop()); + +assert.commandWorked(t.insert([ + {a: [{}, {b: "foo"}]}, + {a: [{b: "foo"}, {}]}, + {a: [{b: 123}, {b: "foo"}]}, + {a: [{b: "foo"}, {b: 123}]}, + {a: [{b: "bar"}, {b: "foo"}]}, + {a: [{b: "bar"}, {b: "baz"}, {b: "foo"}]}, + {a: [{b: "bar"}, {b: "baz"}, {b: 123}]} +])); + +runWithAndWithoutIndex({"a.b": 1}, () => { + assert(arrayEq(t.find({"a.b": "foo"}, {_id: 0}).toArray(), [ + {a: [{}, {b: "foo"}]}, + {a: [{b: "foo"}, {}]}, + {a: [{b: 123}, {b: "foo"}]}, + {a: [{b: "foo"}, {b: 123}]}, + {a: [{b: "bar"}, {b: "foo"}]}, + {a: [{b: "bar"}, {b: "baz"}, {b: "foo"}]} + ])); +}); })(); diff --git a/src/mongo/db/query/sbe_stage_builder_filter.cpp b/src/mongo/db/query/sbe_stage_builder_filter.cpp index aff3137f7da..a9382bd116a 100644 --- a/src/mongo/db/query/sbe_stage_builder_filter.cpp +++ b/src/mongo/db/query/sbe_stage_builder_filter.cpp @@ -112,6 +112,13 @@ struct MatchExpressionVisitorContext { sbe::value::SlotId inputVar; }; +std::unique_ptr<sbe::EExpression> makeFillEmptyFalse(std::unique_ptr<sbe::EExpression> e) { + using namespace std::literals; + return sbe::makeE<sbe::EFunction>( + "fillEmpty"sv, + sbe::makeEs(std::move(e), sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Boolean, 0))); +} + /** * A helper function to generate a path traversal plan stage at the given nested 'level' of the * traversal path. For example, for a dotted path expression {'a.b': 2}, the traversal sub-tree will @@ -201,7 +208,7 @@ std::unique_ptr<sbe::PlanStage> generateTraverseHelper(MatchExpressionVisitorCon sbe::makeE<sbe::EVariable>(traversePredicateVar)); } - // The final traverse stage for the current nested level. + // The traverse stage for the current nested level. return sbe::makeS<sbe::TraverseStage>( std::move(inputStage), std::move(innerBranch), @@ -262,8 +269,8 @@ void generateTraverseForComparisonPredicate(MatchExpressionVisitorContext* conte // SBE EConstant assumes ownership of the value so we have to make a copy here. auto [tag, val] = sbe::value::copyValue(tagView, valView); - return sbe::makeE<sbe::EPrimBinary>( - binaryOp, sbe::makeE<sbe::EVariable>(inputSlot), sbe::makeE<sbe::EConstant>(tag, val)); + return makeFillEmptyFalse(sbe::makeE<sbe::EPrimBinary>( + binaryOp, sbe::makeE<sbe::EVariable>(inputSlot), sbe::makeE<sbe::EConstant>(tag, val))); }; generateTraverse(context, expr, std::move(makeEExprFn)); } @@ -580,15 +587,11 @@ public: // TODO: In the future, this needs to account for the fact that the regex match // expression matches strings, but also matches stored regexes. For example, // {$match: {a: /foo/}} matches the document {a: /foo/} in addition to {a: "foobar"}. - return sbe::makeE<sbe::EPrimBinary>( - sbe::EPrimBinary::logicAnd, - sbe::makeE<sbe::EFunction>("isString", - sbe::makeEs(sbe::makeE<sbe::EVariable>(inputSlot))), - sbe::makeE<sbe::EFunction>( - "regexMatch", - sbe::makeEs( - sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::pcreRegex, ownedRegexVal), - sbe::makeE<sbe::EVariable>(inputSlot)))); + return makeFillEmptyFalse(sbe::makeE<sbe::EFunction>( + "regexMatch", + sbe::makeEs( + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::pcreRegex, ownedRegexVal), + sbe::makeE<sbe::EVariable>(inputSlot)))); }; generateTraverse(_context, expr, std::move(makeEExprFn)); |