summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher/expression_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/matcher/expression_parser.cpp')
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp440
1 files changed, 239 insertions, 201 deletions
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index ddb6491739b..d235e36e964 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -89,15 +89,24 @@ bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) {
return false;
}
-const boost::container::flat_set<StringData> topLevelOperators{"$_internalSchemaAllowedProperties",
- "$_internalSchemaMaxProperties",
- "$_internalSchemaMinProperties",
- "$_internalSchemaXor",
- "$and",
- "$nor",
- "$or",
- "$where"};
-
+const boost::container::flat_set<StringData> kPathlessOperators{"$_internalSchemaAllowedProperties",
+ "$_internalSchemaCond",
+ "$_internalSchemaMaxProperties",
+ "$_internalSchemaMinProperties",
+ "$_internalSchemaRootDocEq",
+ "$_internalSchemaXor",
+ "$alwaysFalse",
+ "$alwaysTrue",
+ "$and",
+ "$atomic",
+ "$comment",
+ "$expr",
+ "$isolated"
+ "$jsonSchema",
+ "$nor",
+ "$or",
+ "$text",
+ "$where"};
} // namespace
namespace mongo {
@@ -145,23 +154,25 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
const BSONElement& e,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
AllowedFeatureSet allowedFeatures,
- bool topLevel) {
+ DocumentParseLevel currentLevel) {
if (mongoutils::str::equals("$eq", e.fieldName()))
return _parseComparison(name, new EqualityMatchExpression(), e, expCtx, allowedFeatures);
if (mongoutils::str::equals("$not", e.fieldName())) {
- return _parseNot(name, e, expCtx, allowedFeatures, topLevel);
+ return _parseNot(name, e, expCtx, allowedFeatures, currentLevel);
}
auto parseExpMatchType = MatchExpressionParser::parsePathAcceptingKeyword(e);
if (!parseExpMatchType) {
- // $where cannot be a sub-expression because it works on top-level documents only.
- if (mongoutils::str::equals("$where", e.fieldName())) {
- return {Status(ErrorCodes::BadValue, "$where cannot be applied to a field")};
+ if (kPathlessOperators.find(e.fieldNameStringData()) != kPathlessOperators.end()) {
+ return {Status(ErrorCodes::FailedToParse,
+ mongoutils::str::stream() << e.fieldNameStringData()
+ << " cannot be applied to a field path.")};
}
- return {Status(ErrorCodes::BadValue,
- mongoutils::str::stream() << "unknown operator: " << e.fieldName())};
+ return {
+ Status(ErrorCodes::BadValue,
+ mongoutils::str::stream() << "unknown operator: " << e.fieldNameStringData())};
}
switch (*parseExpMatchType) {
@@ -299,10 +310,10 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
}
case PathAcceptingKeyword::ELEM_MATCH:
- return _parseElemMatch(name, e, expCtx, allowedFeatures, topLevel);
+ return _parseElemMatch(name, e, expCtx, allowedFeatures);
case PathAcceptingKeyword::ALL:
- return _parseAll(name, e, expCtx, allowedFeatures, topLevel);
+ return _parseAll(name, e, expCtx, allowedFeatures);
case PathAcceptingKeyword::WITHIN:
case PathAcceptingKeyword::GEO_INTERSECTS:
@@ -349,7 +360,8 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
str::stream() << "$_internalSchemaObjectMatch must be an object");
}
- auto parsedSubObjExpr = _parse(e.Obj(), expCtx, allowedFeatures, topLevel);
+ auto parsedSubObjExpr =
+ _parse(e.Obj(), expCtx, allowedFeatures, DocumentParseLevel::kUserSubDocument);
if (!parsedSubObjExpr.isOK()) {
return parsedSubObjExpr;
}
@@ -387,7 +399,8 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
}
case PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX: {
- return _parseInternalSchemaMatchArrayIndex(name, e, expCtx);
+ return _parseInternalSchemaMatchArrayIndex(
+ name, e, expCtx, allowedFeatures, currentLevel);
}
case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX: {
@@ -435,8 +448,17 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
<< "must be an object");
}
+ auto filter = _parse(second.embeddedObject(),
+ expCtx,
+ kBanAllSpecialFeatures,
+ DocumentParseLevel::kUserSubDocument);
+
+ if (!filter.isOK()) {
+ return filter.getStatus();
+ }
+
auto exprWithPlaceholder =
- ExpressionWithPlaceholder::parse(second.embeddedObject(), expCtx);
+ ExpressionWithPlaceholder::make(std::move(filter.getValue()));
if (!exprWithPlaceholder.isOK()) {
return exprWithPlaceholder.getStatus();
}
@@ -472,10 +494,12 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
const BSONObj& obj,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
AllowedFeatureSet allowedFeatures,
- bool topLevel) {
+ DocumentParseLevel currentLevel) {
std::unique_ptr<AndMatchExpression> root = stdx::make_unique<AndMatchExpression>();
- const bool childIsTopLevel = false;
+ const DocumentParseLevel nextLevel = (currentLevel == DocumentParseLevel::kPredicateTopLevel)
+ ? DocumentParseLevel::kUserDocumentTopLevel
+ : currentLevel;
BSONObjIterator i(obj);
while (i.more()) {
BSONElement e = i.next();
@@ -486,8 +510,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$or must be an array")};
std::unique_ptr<OrMatchExpression> temp = stdx::make_unique<OrMatchExpression>();
- Status s =
- _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, childIsTopLevel);
+ Status s = _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, nextLevel);
if (!s.isOK())
return s;
root->add(temp.release());
@@ -495,8 +518,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$and must be an array")};
std::unique_ptr<AndMatchExpression> temp = stdx::make_unique<AndMatchExpression>();
- Status s =
- _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, childIsTopLevel);
+ Status s = _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, nextLevel);
if (!s.isOK())
return s;
root->add(temp.release());
@@ -504,16 +526,16 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$nor must be an array")};
std::unique_ptr<NorMatchExpression> temp = stdx::make_unique<NorMatchExpression>();
- Status s =
- _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, childIsTopLevel);
+ Status s = _parseTreeList(e.Obj(), temp.get(), expCtx, allowedFeatures, nextLevel);
if (!s.isOK())
return s;
root->add(temp.release());
} else if (mongoutils::str::equals("atomic", rest) ||
mongoutils::str::equals("isolated", rest)) {
- if (!topLevel)
- return {Status(ErrorCodes::BadValue,
+ if (currentLevel != DocumentParseLevel::kPredicateTopLevel) {
+ return {Status(ErrorCodes::FailedToParse,
"$atomic/$isolated has to be at the top level")};
+ }
// Don't do anything with the expression; CanonicalQuery::init() will look through
// the BSONObj again for a $atomic/$isolated.
} else if (mongoutils::str::equals("where", rest)) {
@@ -521,13 +543,19 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
return {Status(ErrorCodes::BadValue, "$where is not allowed in this context")};
}
+ if (currentLevel == DocumentParseLevel::kUserSubDocument) {
+ return {Status(ErrorCodes::FailedToParse,
+ "$where can only be applied to the top-level document")};
+ }
+
StatusWithMatchExpression s = _extensionsCallback->parseWhere(e);
if (!s.isOK())
return s;
root->add(s.getValue().release());
} else if (mongoutils::str::equals("expr", rest)) {
- if (!topLevel) {
- return {Status(ErrorCodes::BadValue, "$expr has to be at the top level")};
+ if (currentLevel == DocumentParseLevel::kUserSubDocument) {
+ return {Status(ErrorCodes::FailedToParse,
+ "$expr can only be applied to the top-level document")};
}
auto status = _parseExpr(e, allowedFeatures, expCtx);
@@ -558,7 +586,8 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
root->add(eq.release());
} else if (mongoutils::str::equals("_internalSchemaAllowedProperties", rest)) {
- auto allowedProperties = _parseInternalSchemaAllowedProperties(e, expCtx);
+ auto allowedProperties =
+ _parseInternalSchemaAllowedProperties(e, expCtx, allowedFeatures, nextLevel);
if (!allowedProperties.isOK()) {
return allowedProperties.getStatus();
}
@@ -566,7 +595,11 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
} else if (mongoutils::str::equals("_internalSchemaCond", rest)) {
auto condExpr =
_parseInternalSchemaFixedArityArgument<InternalSchemaCondMatchExpression>(
- InternalSchemaCondMatchExpression::kName, e, expCtx, allowedFeatures);
+ InternalSchemaCondMatchExpression::kName,
+ e,
+ expCtx,
+ allowedFeatures,
+ nextLevel);
if (!condExpr.isOK()) {
return condExpr.getStatus();
}
@@ -577,8 +610,8 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
return {
Status(ErrorCodes::TypeMismatch, "$_internalSchemaXor must be an array")};
auto xorExpr = stdx::make_unique<InternalSchemaXorMatchExpression>();
- Status s = _parseTreeList(
- e.Obj(), xorExpr.get(), expCtx, allowedFeatures, childIsTopLevel);
+ Status s =
+ _parseTreeList(e.Obj(), xorExpr.get(), expCtx, allowedFeatures, nextLevel);
if (!s.isOK())
return s;
root->add(xorExpr.release());
@@ -639,7 +672,7 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
auto alwaysTrueExpr = stdx::make_unique<AlwaysTrueMatchExpression>();
root->add(alwaysTrueExpr.release());
} else if (mongoutils::str::equals("_internalSchemaRootDocEq", rest)) {
- if (!topLevel) {
+ if (currentLevel != DocumentParseLevel::kPredicateTopLevel) {
return {Status(ErrorCodes::FailedToParse,
str::stream() << InternalSchemaRootDocEqMatchExpression::kName
<< " must be at the top level")};
@@ -664,8 +697,8 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
}
if (_isExpressionDocument(e, false)) {
- Status s = _parseSub(
- e.fieldName(), e.Obj(), root.get(), expCtx, allowedFeatures, childIsTopLevel);
+ Status s =
+ _parseSub(e.fieldName(), e.Obj(), root.get(), expCtx, allowedFeatures, nextLevel);
if (!s.isOK())
return s;
continue;
@@ -701,7 +734,7 @@ Status MatchExpressionParser::_parseSub(const char* name,
AndMatchExpression* root,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
AllowedFeatureSet allowedFeatures,
- bool topLevel) {
+ DocumentParseLevel currentLevel) {
// The one exception to {field : {fully contained argument} } is, of course, geo. Example:
// sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } }
// We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over
@@ -733,9 +766,8 @@ Status MatchExpressionParser::_parseSub(const char* name,
while (j.more()) {
BSONElement deep = j.next();
- const bool childIsTopLevel = false;
StatusWithMatchExpression s =
- _parseSubField(sub, root, name, deep, expCtx, allowedFeatures, childIsTopLevel);
+ _parseSubField(sub, root, name, deep, expCtx, allowedFeatures, currentLevel);
if (!s.isOK())
return s.getStatus();
@@ -945,21 +977,15 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(
const char* name,
const BSONElement& e,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
- AllowedFeatureSet allowedFeatures,
- bool topLevel) {
+ AllowedFeatureSet allowedFeatures) {
if (e.type() != Object)
return {Status(ErrorCodes::BadValue, "$elemMatch needs an Object")};
BSONObj obj = e.Obj();
- // $elemMatch value case applies when the children all
- // work on the field 'name'.
- // This is the case when:
- // 1) the argument is an expression document; and
- // 2) expression is not a AND/NOR/OR logical operator. Children of
- // these logical operators are initialized with field names.
- // 3) expression is not a WHERE operator. WHERE works on objects instead
- // of specific field.
+ // We distinguish here between $elemMatch value and $elemMatch object. The $elemMatch can only
+ // be $elemMatch value if the argument is an expression document whose operator is
+ // path-accepting.
bool isElemMatchValue = false;
if (_isExpressionDocument(e, true)) {
BSONObj o = e.Obj();
@@ -967,14 +993,15 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(
invariant(!elt.eoo());
isElemMatchValue =
- topLevelOperators.find(elt.fieldNameStringData()) == topLevelOperators.end();
+ kPathlessOperators.find(elt.fieldNameStringData()) == kPathlessOperators.end();
}
if (isElemMatchValue) {
// value case
AndMatchExpression theAnd;
- Status s = _parseSub("", obj, &theAnd, expCtx, allowedFeatures, topLevel);
+ Status s = _parseSub(
+ "", obj, &theAnd, expCtx, allowedFeatures, DocumentParseLevel::kUserSubDocument);
if (!s.isOK())
return s;
@@ -998,17 +1025,12 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(
// object case
- StatusWithMatchExpression subRaw = _parse(obj, expCtx, allowedFeatures, topLevel);
+ StatusWithMatchExpression subRaw =
+ _parse(obj, expCtx, allowedFeatures, DocumentParseLevel::kUserSubDocument);
if (!subRaw.isOK())
return subRaw;
std::unique_ptr<MatchExpression> sub = std::move(subRaw.getValue());
- // $where is not supported under $elemMatch because $where
- // applies to top-level document, not array elements in a field.
- if (hasNode(sub.get(), MatchExpression::WHERE)) {
- return {Status(ErrorCodes::BadValue, "$elemMatch cannot contain $where expression")};
- }
-
std::unique_ptr<ElemMatchObjectMatchExpression> temp =
stdx::make_unique<ElemMatchObjectMatchExpression>();
Status status = temp->init(name, sub.release());
@@ -1022,8 +1044,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseAll(
const char* name,
const BSONElement& e,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
- AllowedFeatureSet allowedFeatures,
- bool topLevel) {
+ AllowedFeatureSet allowedFeatures) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$all needs an array")};
@@ -1051,12 +1072,8 @@ StatusWithMatchExpression MatchExpressionParser::_parseAll(
return {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")};
}
- const bool childIsTopLevel = false;
- StatusWithMatchExpression inner = _parseElemMatch(name,
- hopefullyElemMatchObj.firstElement(),
- expCtx,
- allowedFeatures,
- childIsTopLevel);
+ StatusWithMatchExpression inner = _parseElemMatch(
+ name, hopefullyElemMatchObj.firstElement(), expCtx, allowedFeatures);
if (!inner.isOK())
return inner;
myAnd->add(inner.getValue().release());
@@ -1267,6 +1284,128 @@ StatusWith<long long> MatchExpressionParser::parseIntegerElementToLong(BSONEleme
return number;
}
+StatusWith<std::unique_ptr<ExpressionWithPlaceholder>>
+MatchExpressionParser::_parseExprWithPlaceholder(
+ const BSONObj& containingObject,
+ StringData exprWithPlaceholderFieldName,
+ StringData expressionName,
+ StringData expectedPlaceholder,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ AllowedFeatureSet allowedFeatures,
+ DocumentParseLevel currentLevel) {
+ auto exprWithPlaceholderElem = containingObject[exprWithPlaceholderFieldName];
+ if (!exprWithPlaceholderElem) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << expressionName << " requires '" << exprWithPlaceholderFieldName
+ << "'"};
+ } else if (exprWithPlaceholderElem.type() != BSONType::Object) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << expressionName << " found '" << exprWithPlaceholderFieldName
+ << "', which is an incompatible type: "
+ << exprWithPlaceholderElem.type()};
+ }
+
+ auto filter = _parse(
+ exprWithPlaceholderElem.embeddedObject(), expCtx, kBanAllSpecialFeatures, currentLevel);
+
+ if (!filter.isOK()) {
+ return filter.getStatus();
+ }
+
+ auto result = ExpressionWithPlaceholder::make(std::move(filter.getValue()));
+ if (!result.isOK()) {
+ return result.getStatus();
+ }
+
+ auto placeholder = result.getValue()->getPlaceholder();
+ if (placeholder && (*placeholder != expectedPlaceholder)) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << expressionName << " expected a name placeholder of "
+ << expectedPlaceholder
+ << ", but '"
+ << exprWithPlaceholderElem.fieldName()
+ << "' has a mismatching placeholder '"
+ << *placeholder
+ << "'"};
+ }
+ return result;
+}
+
+StatusWith<std::vector<InternalSchemaAllowedPropertiesMatchExpression::PatternSchema>>
+MatchExpressionParser::_parsePatternProperties(
+ BSONElement patternPropertiesElem,
+ StringData expectedPlaceholder,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ AllowedFeatureSet allowedFeatures,
+ DocumentParseLevel currentLevel) {
+ if (!patternPropertiesElem) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires 'patternProperties'"};
+ } else if (patternPropertiesElem.type() != BSONType::Array) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires 'patternProperties' to be an array, not "
+ << patternPropertiesElem.type()};
+ }
+
+ std::vector<InternalSchemaAllowedPropertiesMatchExpression::PatternSchema> patternProperties;
+ for (auto&& constraintElem : patternPropertiesElem.embeddedObject()) {
+ if (constraintElem.type() != BSONType::Object) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires 'patternProperties' to be an array of objects"};
+ }
+
+ auto constraint = constraintElem.embeddedObject();
+ if (constraint.nFields() != 2) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires 'patternProperties' to be an array of objects "
+ "containing exactly two fields, 'regex' and 'expression'"};
+ }
+
+ auto expressionWithPlaceholder =
+ _parseExprWithPlaceholder(constraint,
+ "expression"_sd,
+ InternalSchemaAllowedPropertiesMatchExpression::kName,
+ expectedPlaceholder,
+ expCtx,
+ allowedFeatures,
+ currentLevel);
+ if (!expressionWithPlaceholder.isOK()) {
+ return expressionWithPlaceholder.getStatus();
+ }
+
+ auto regexElem = constraint["regex"];
+ if (!regexElem) {
+ return {
+ ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires each object in 'patternProperties' to have a 'regex'"};
+ }
+ if (regexElem.type() != BSONType::RegEx) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " requires 'patternProperties' to be an array of objects, "
+ "where 'regex' is a regular expression"};
+ }
+ if (*regexElem.regexFlags() != '\0') {
+ return {
+ ErrorCodes::BadValue,
+ str::stream()
+ << InternalSchemaAllowedPropertiesMatchExpression::kName
+ << " does not accept regex flags for pattern schemas in 'patternProperties'"};
+ }
+
+ patternProperties.emplace_back(
+ InternalSchemaAllowedPropertiesMatchExpression::Pattern(regexElem.regex()),
+ std::move(expressionWithPlaceholder.getValue()));
+ }
+
+ return std::move(patternProperties);
+}
+
StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaFmod(const char* name,
const BSONElement& elem) {
StringData path(name);
@@ -1307,7 +1446,8 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaFixedArityA
StringData name,
const BSONElement& input,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
- AllowedFeatureSet allowedFeatures) {
+ AllowedFeatureSet allowedFeatures,
+ DocumentParseLevel currentLevel) {
constexpr auto arity = T::arity();
if (input.type() != BSONType::Array) {
return {ErrorCodes::FailedToParse,
@@ -1335,8 +1475,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaFixedArityA
<< elem.type()};
}
- const bool isTopLevel = false;
- auto subexpr = _parse(elem.embeddedObject(), expCtx, allowedFeatures, isTopLevel);
+ auto subexpr = _parse(elem.embeddedObject(), expCtx, allowedFeatures, currentLevel);
if (!subexpr.isOK()) {
return subexpr.getStatus();
}
@@ -1404,120 +1543,6 @@ StatusWith<StringData> parseNamePlaceholder(const BSONObj& containingObject,
return {namePlaceholderElem.valueStringData()};
}
-/**
- * Looks at the field named 'exprWithPlaceholderFieldName' within 'containingObject' and parses an
- * ExpressionWithPlaceholder from that element. Fails if an error occurs during parsing, or if the
- * ExpressionWithPlaceholder has a different name placeholder than 'expectedPlaceholder'.
- * 'expressionName' is the name of the expression that requires the ExpressionWithPlaceholder and is
- * used to generate helpful error messages.
- */
-StatusWith<std::unique_ptr<ExpressionWithPlaceholder>> parseExprWithPlaceholder(
- const BSONObj& containingObject,
- StringData exprWithPlaceholderFieldName,
- StringData expressionName,
- StringData expectedPlaceholder,
- const boost::intrusive_ptr<ExpressionContext>& expCtx) {
- auto exprWithPlaceholderElem = containingObject[exprWithPlaceholderFieldName];
- if (!exprWithPlaceholderElem) {
- return {ErrorCodes::FailedToParse,
- str::stream() << expressionName << " requires '" << exprWithPlaceholderFieldName
- << "'"};
- } else if (exprWithPlaceholderElem.type() != BSONType::Object) {
- return {ErrorCodes::TypeMismatch,
- str::stream() << expressionName << " found '" << exprWithPlaceholderFieldName
- << "', which is an incompatible type: "
- << exprWithPlaceholderElem.type()};
- }
-
- auto result =
- ExpressionWithPlaceholder::parse(exprWithPlaceholderElem.embeddedObject(), expCtx);
- if (!result.isOK()) {
- return result.getStatus();
- }
-
- auto placeholder = result.getValue()->getPlaceholder();
- if (placeholder && (*placeholder != expectedPlaceholder)) {
- return {ErrorCodes::FailedToParse,
- str::stream() << expressionName << " expected a name placeholder of "
- << expectedPlaceholder
- << ", but '"
- << exprWithPlaceholderElem.fieldName()
- << "' has a mismatching placeholder '"
- << *placeholder
- << "'"};
- }
- return result;
-}
-
-StatusWith<std::vector<InternalSchemaAllowedPropertiesMatchExpression::PatternSchema>>
-parsePatternProperties(BSONElement patternPropertiesElem,
- StringData expectedPlaceholder,
- const boost::intrusive_ptr<ExpressionContext>& expCtx) {
- if (!patternPropertiesElem) {
- return {ErrorCodes::FailedToParse,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires 'patternProperties'"};
- } else if (patternPropertiesElem.type() != BSONType::Array) {
- return {ErrorCodes::TypeMismatch,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires 'patternProperties' to be an array, not "
- << patternPropertiesElem.type()};
- }
-
- std::vector<InternalSchemaAllowedPropertiesMatchExpression::PatternSchema> patternProperties;
- for (auto&& constraintElem : patternPropertiesElem.embeddedObject()) {
- if (constraintElem.type() != BSONType::Object) {
- return {ErrorCodes::TypeMismatch,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires 'patternProperties' to be an array of objects"};
- }
-
- auto constraint = constraintElem.embeddedObject();
- if (constraint.nFields() != 2) {
- return {ErrorCodes::FailedToParse,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires 'patternProperties' to be an array of objects "
- "containing exactly two fields, 'regex' and 'expression'"};
- }
-
- auto expressionWithPlaceholder =
- parseExprWithPlaceholder(constraint,
- "expression"_sd,
- InternalSchemaAllowedPropertiesMatchExpression::kName,
- expectedPlaceholder,
- expCtx);
- if (!expressionWithPlaceholder.isOK()) {
- return expressionWithPlaceholder.getStatus();
- }
-
- auto regexElem = constraint["regex"];
- if (!regexElem) {
- return {
- ErrorCodes::FailedToParse,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires each object in 'patternProperties' to have a 'regex'"};
- }
- if (regexElem.type() != BSONType::RegEx) {
- return {ErrorCodes::TypeMismatch,
- str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " requires 'patternProperties' to be an array of objects, "
- "where 'regex' is a regular expression"};
- } else if (*regexElem.regexFlags() != '\0') {
- return {
- ErrorCodes::BadValue,
- str::stream()
- << InternalSchemaAllowedPropertiesMatchExpression::kName
- << " does not accept regex flags for pattern schemas in 'patternProperties'"};
- }
-
- patternProperties.emplace_back(
- InternalSchemaAllowedPropertiesMatchExpression::Pattern(regexElem.regex()),
- std::move(expressionWithPlaceholder.getValue()));
- }
-
- return std::move(patternProperties);
-}
-
StatusWith<boost::container::flat_set<StringData>> parseProperties(BSONElement propertiesElem) {
if (!propertiesElem) {
return {ErrorCodes::FailedToParse,
@@ -1549,7 +1574,9 @@ StatusWith<boost::container::flat_set<StringData>> parseProperties(BSONElement p
StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaMatchArrayIndex(
const char* path,
const BSONElement& elem,
- const boost::intrusive_ptr<ExpressionContext>& expCtx) {
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ AllowedFeatureSet allowedFeatures,
+ DocumentParseLevel currentLevel) {
if (elem.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
str::stream() << InternalSchemaMatchArrayIndexMatchExpression::kName
@@ -1576,11 +1603,13 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaMatchArrayI
}
auto expressionWithPlaceholder =
- parseExprWithPlaceholder(subobj,
- "expression"_sd,
- InternalSchemaMatchArrayIndexMatchExpression::kName,
- namePlaceholder.getValue(),
- expCtx);
+ _parseExprWithPlaceholder(subobj,
+ "expression"_sd,
+ InternalSchemaMatchArrayIndexMatchExpression::kName,
+ namePlaceholder.getValue(),
+ expCtx,
+ allowedFeatures,
+ currentLevel);
if (!expressionWithPlaceholder.isOK()) {
return expressionWithPlaceholder.getStatus();
}
@@ -1595,7 +1624,10 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaMatchArrayI
}
StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaAllowedProperties(
- const BSONElement& elem, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
+ const BSONElement& elem,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ AllowedFeatureSet allowedFeatures,
+ DocumentParseLevel currentLevel) {
if (elem.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
str::stream() << InternalSchemaAllowedPropertiesMatchExpression::kName
@@ -1616,17 +1648,23 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaAllowedProp
return namePlaceholder.getStatus();
}
- auto patternProperties =
- parsePatternProperties(subobj["patternProperties"], namePlaceholder.getValue(), expCtx);
+ auto patternProperties = _parsePatternProperties(subobj["patternProperties"],
+ namePlaceholder.getValue(),
+ expCtx,
+ allowedFeatures,
+ currentLevel);
if (!patternProperties.isOK()) {
return patternProperties.getStatus();
}
- auto otherwise = parseExprWithPlaceholder(subobj,
- "otherwise"_sd,
- InternalSchemaAllowedPropertiesMatchExpression::kName,
- namePlaceholder.getValue(),
- expCtx);
+ auto otherwise =
+ _parseExprWithPlaceholder(subobj,
+ "otherwise"_sd,
+ InternalSchemaAllowedPropertiesMatchExpression::kName,
+ namePlaceholder.getValue(),
+ expCtx,
+ allowedFeatures,
+ currentLevel);
if (!otherwise.isOK()) {
return otherwise.getStatus();
}