summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvan Nixon <evan.nixon@10gen.com>2019-02-12 14:39:17 -0800
committerEvan Nixon <evan.nixon@10gen.com>2019-02-12 15:13:48 -0800
commit613454eb99abe25682f9a50d93ee04e7e90ba314 (patch)
tree8dacb34a5497791cd9936f8d217fd0610917290e
parent2c65bbe94d04ac0fa62f4fc51a2ece2e748de739 (diff)
downloadmongo-613454eb99abe25682f9a50d93ee04e7e90ba314.tar.gz
SERVER-38621 Do not ignore regex options when specified first
-rw-r--r--jstests/core/regex.js19
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp17
-rw-r--r--src/mongo/db/matcher/expression_parser_test.cpp61
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());