diff options
author | Evan Nixon <evan.nixon@10gen.com> | 2019-02-12 14:39:17 -0800 |
---|---|---|
committer | Evan Nixon <evan.nixon@10gen.com> | 2019-02-12 15:13:48 -0800 |
commit | 613454eb99abe25682f9a50d93ee04e7e90ba314 (patch) | |
tree | 8dacb34a5497791cd9936f8d217fd0610917290e | |
parent | 2c65bbe94d04ac0fa62f4fc51a2ece2e748de739 (diff) | |
download | mongo-613454eb99abe25682f9a50d93ee04e7e90ba314.tar.gz |
SERVER-38621 Do not ignore regex options when specified first
-rw-r--r-- | jstests/core/regex.js | 19 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser_test.cpp | 61 |
3 files changed, 95 insertions, 2 deletions
diff --git a/jstests/core/regex.js b/jstests/core/regex.js index e891ddf77fa..1c6a9d6a3bb 100644 --- a/jstests/core/regex.js +++ b/jstests/core/regex.js @@ -61,4 +61,23 @@ assert.throws(function() { t.find({key: {$regex: 'abcd\0xyz'}}).explain(); }); + + // + // Confirm $options and mode specified in $regex are not allowed to be specified together. + // + t.drop(); + assert.commandWorked(t.insert({x: ["abc"]})); + + let regexFirst = assert.throws(() => t.find({x: {$regex: /ab/i, $options: 's'}}).itcount()); + assert.commandFailedWithCode(regexFirst, 51075); + + let optsFirst = assert.throws(() => t.find({x: {$options: 's', $regex: /ab/i}}).itcount()); + assert.commandFailedWithCode(optsFirst, 51074); + + t.drop(); + assert.commandWorked(t.save({x: ["abc"]})); + + assert.eq(1, t.count({x: {$regex: /ABC/i}})); + assert.eq(1, t.count({x: {$regex: /ABC/, $options: 'i'}})); + assert.eq(1, t.count({x: {$options: 'i', $regex: /ABC/}})); })(); diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 13e75b92859..185952e97e6 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -549,15 +549,28 @@ StatusWithMatchExpression parseRegexDocument(StringData name, const BSONObj& doc regex = e.valueStringData(); } else if (e.type() == BSONType::RegEx) { regex = e.regex(); - regexOptions = e.regexFlags(); + if (!StringData{e.regexFlags()}.empty()) { + if (!regexOptions.empty()) { + return {Status(ErrorCodes::Error(51074), + "options set in both $regex and $options")}; + } + regexOptions = e.regexFlags(); + } } else { return {Status(ErrorCodes::BadValue, "$regex has to be a string")}; } break; case PathAcceptingKeyword::OPTIONS: - if (e.type() != BSONType::String) + if (e.type() != BSONType::String) { return {Status(ErrorCodes::BadValue, "$options has to be a string")}; + } + + if (!regexOptions.empty()) { + return {Status(ErrorCodes::Error(51075), + "options set in both $regex and $options")}; + } + regexOptions = e.valueStringData(); break; default: diff --git a/src/mongo/db/matcher/expression_parser_test.cpp b/src/mongo/db/matcher/expression_parser_test.cpp index 9ac6125f684..d0ee2f1dea7 100644 --- a/src/mongo/db/matcher/expression_parser_test.cpp +++ b/src/mongo/db/matcher/expression_parser_test.cpp @@ -327,6 +327,67 @@ TEST(MatchExpressionParserTest, WhereParsesSuccessfullyWhenAllowed) { .getStatus()); } +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithoutOptions) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsInline) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "i"))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithoutOptionsInlineAndEmptyOptionsStr) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" + << "")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexDoesNotParseSuccessfullyWithOptionsInlineAndEmptyOptionsStr) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "i") << "$options" + << "")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsNotInline) { + auto query = BSON("a" << BSON("$regex" << BSONRegEx("/myRegex/", "") << "$options" + << "i")); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexDoesNotParseSuccessfullyWithMultipleOptions) { + auto query = BSON("a" << BSON("$options" + << "s" + << "$regex" + << BSONRegEx("/myRegex/", "i"))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsFirst) { + auto query = BSON("a" << BSON("$options" + << "s" + << "$regex" + << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + +TEST(MatchExpressionParserTest, RegexParsesSuccessfullyWithOptionsFirstEmptyOptions) { + auto query = BSON("a" << BSON("$options" + << "" + << "$regex" + << BSONRegEx("/myRegex/", ""))); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus()); +} + + TEST(MatchExpressionParserTest, NearSphereFailsToParseWhenDisallowed) { auto query = fromjson("{a: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}"); boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); |