diff options
author | James Wahlin <james.wahlin@10gen.com> | 2017-01-12 17:44:52 -0500 |
---|---|---|
committer | James Wahlin <james.wahlin@10gen.com> | 2017-01-19 12:37:23 -0500 |
commit | 8a5f459539b651cccefc35ebe209e30af2b1b9f1 (patch) | |
tree | 830563ecb8347f25e9f8bcc66332f2ed9b1c5b1b | |
parent | 4e62cebd32bfc6b5f3283b111a9a79b25ca3d791 (diff) | |
download | mongo-8a5f459539b651cccefc35ebe209e30af2b1b9f1.tar.gz |
SERVER-27649 $elemMatch serialization $regex support
(cherry picked from commit 04b16905b7c3b6b3fce5fbc7f6ba17522311a7c4)
-rw-r--r-- | jstests/core/regex.js | 36 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.h | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_serialization_test.cpp | 100 |
4 files changed, 135 insertions, 16 deletions
diff --git a/jstests/core/regex.js b/jstests/core/regex.js index d6982678f97..e891ddf77fa 100644 --- a/jstests/core/regex.js +++ b/jstests/core/regex.js @@ -1,32 +1,56 @@ (function() { 'use strict'; - var t = db.jstests_regex; + const t = db.jstests_regex; + + const isMaster = db.runCommand("ismaster"); + assert.commandWorked(isMaster); + const isMongos = (isMaster.msg === "isdbgrid"); t.drop(); - t.save({a: "bcd"}); + assert.writeOK(t.save({a: "bcd"})); assert.eq(1, t.count({a: /b/}), "A"); assert.eq(1, t.count({a: /bc/}), "B"); assert.eq(1, t.count({a: /bcd/}), "C"); assert.eq(0, t.count({a: /bcde/}), "D"); t.drop(); - t.save({a: {b: "cde"}}); + assert.writeOK(t.save({a: {b: "cde"}})); assert.eq(1, t.count({'a.b': /de/}), "E"); t.drop(); - t.save({a: {b: ["cde"]}}); + assert.writeOK(t.save({a: {b: ["cde"]}})); assert.eq(1, t.count({'a.b': /de/}), "F"); t.drop(); - t.save({a: [{b: "cde"}]}); + assert.writeOK(t.save({a: [{b: "cde"}]})); assert.eq(1, t.count({'a.b': /de/}), "G"); t.drop(); - t.save({a: [{b: ["cde"]}]}); + assert.writeOK(t.save({a: [{b: ["cde"]}]})); assert.eq(1, t.count({'a.b': /de/}), "H"); + // + // Confirm match and explain serialization for $elemMatch with $regex. + // + t.drop(); + assert.writeOK(t.insert({x: ["abc"]})); + + const query = {x: {$elemMatch: {$regex: 'ABC', $options: 'i'}}}; + assert.eq(1, t.count(query)); + + const result = t.find(query).explain(); + assert.commandWorked(result); + + if (!isMongos) { + assert(result.hasOwnProperty("queryPlanner")); + assert(result.queryPlanner.hasOwnProperty("parsedQuery"), tojson(result)); + assert.eq(result.queryPlanner.parsedQuery, query); + } + + // // Disallow embedded null bytes when using $regex syntax. + // t.drop(); assert.throws(function() { t.find({a: {$regex: "a\0b", $options: "i"}}).itcount(); diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp index 90b0cc985dc..d562bff141e 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -319,6 +319,17 @@ void RegexMatchExpression::debugString(StringBuilder& debug, int level) const { } void RegexMatchExpression::serialize(BSONObjBuilder* out) const { + BSONObjBuilder regexBuilder(out->subobjStart(path())); + regexBuilder.append("$regex", _regex); + + if (!_flags.empty()) { + regexBuilder.append("$options", _flags); + } + + regexBuilder.doneFast(); +} + +void RegexMatchExpression::serializeToBSONTypeRegex(BSONObjBuilder* out) const { out->appendRegex(path(), _regex, _flags); } @@ -596,7 +607,7 @@ void InMatchExpression::serialize(BSONObjBuilder* out) const { } for (auto&& _regex : _regexes) { BSONObjBuilder regexBob; - _regex->serialize(®exBob); + _regex->serializeToBSONTypeRegex(®exBob); arrBob.append(regexBob.obj().firstElement()); } arrBob.doneFast(); diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index 26568b20b81..128d3070737 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -247,6 +247,8 @@ public: virtual void serialize(BSONObjBuilder* out) const; + void serializeToBSONTypeRegex(BSONObjBuilder* out) const; + void shortDebugString(StringBuilder& debug) const; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 572ae25d585..13a2aaa172d 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -166,6 +166,28 @@ TEST(SerializeBasic, ExpressionElemMatchValueSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionElemMatchValueWithRegexSerializesCorrectly) { + const CollatorInterface* collator = nullptr; + const auto match = BSON("x" << BSON("$elemMatch" << BSON("$regex" + << "abc" + << "$options" + << "i"))); + Matcher original(match, ExtensionsCallbackNoop(), collator); + Matcher reserialized( + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), match); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{x: ['abc', 'xyz']}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: ['ABC', 'XYZ']}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: ['def', 'xyz']}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + TEST(SerializeBasic, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly) { const CollatorInterface* collator = nullptr; Matcher original( @@ -233,7 +255,12 @@ TEST(SerializeBasic, ExpressionAllWithRegex) { fromjson("{x: {$all: [/a.b.c/, /.d.e./]}}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: /a.b.c/}, {x: /.d.e./}]}")); + + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("$and" << BSON_ARRAY(BSON("x" << BSON("$regex" + << "a.b.c")) + << BSON("x" << BSON("$regex" + << ".d.e."))))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abcde'}"); @@ -276,6 +303,26 @@ TEST(SerializeBasic, ExpressionNeSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionNeWithRegexObjectSerializesCorrectly) { + const CollatorInterface* collator = nullptr; + Matcher original(BSON("x" << BSON("$ne" << BSON("$regex" + << "abc"))), + ExtensionsCallbackNoop(), + collator); + Matcher reserialized( + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$eq" << BSON("$regex" + << "abc")))))); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{x: {a: 1}}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: {a: [1, 2]}}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + TEST(SerializeBasic, ExpressionLtSerializesCorrectly) { const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$lt: 3}}"), ExtensionsCallbackNoop(), collator); @@ -341,7 +388,9 @@ TEST(SerializeBasic, ExpressionRegexWithObjSerializesCorrectly) { Matcher original(fromjson("{x: {$regex: 'a.b'}}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b'}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("x" << BSON("$regex" + << "a.b"))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); @@ -351,12 +400,16 @@ TEST(SerializeBasic, ExpressionRegexWithObjSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } -TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { +TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) { const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: /a.b/i}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b', $options: 'i'}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("x" << BSON("$regex" + << "a.b" + << "$options" + << "i"))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); @@ -366,12 +419,14 @@ TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } -TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) { +TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: /a.b/}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b'}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("x" << BSON("$regex" + << "a.b"))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); @@ -510,6 +565,25 @@ TEST(SerializeBasic, ExpressionNinSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionNinWithRegexValueSerializesCorrectly) { + const CollatorInterface* collator = nullptr; + Matcher original( + fromjson("{x: {$nin: [/abc/, /def/, /xyz/]}}"), ExtensionsCallbackNoop(), collator); + Matcher reserialized( + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{$nor: [{x: {$in: [/abc/, /def/, /xyz/]}}]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{x: 'abc'}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: 'def'}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + obj = fromjson("{x: [/abc/, /def/]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + TEST(SerializeBasic, ExpressionBitsAllSetSerializesCorrectly) { const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$bitsAllSet: [1, 3]}}"), ExtensionsCallbackNoop(), collator); @@ -626,7 +700,9 @@ TEST(SerializeBasic, ExpressionNotWithRegexObjSerializesCorrectly) { Matcher original(fromjson("{x: {$not: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/}]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" + << "a.b"))))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); @@ -641,7 +717,9 @@ TEST(SerializeBasic, ExpressionNotWithRegexValueSerializesCorrectly) { Matcher original(fromjson("{x: {$not: /a.b/}}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/}]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" + << "a.b"))))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); @@ -656,7 +734,11 @@ TEST(SerializeBasic, ExpressionNotWithRegexValueAndOptionsSerializesCorrectly) { Matcher original(fromjson("{x: {$not: /a.b/i}}"), ExtensionsCallbackNoop(), collator); Matcher reserialized( serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/i}]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" + << "a.b" + << "$options" + << "i"))))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 'abc'}"); |