// expression_parser.cpp
/**
* Copyright (C) 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/matcher/expression_array.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/namespace_string.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/mongoutils/str.h"
namespace {
using namespace mongo;
/**
* Returns true if subtree contains MatchExpression 'type'.
*/
bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) {
if (type == root->matchType()) {
return true;
}
for (size_t i = 0; i < root->numChildren(); ++i) {
if (hasNode(root->getChild(i), type)) {
return true;
}
}
return false;
}
} // namespace
namespace mongo {
using std::string;
using stdx::make_unique;
StatusWithMatchExpression MatchExpressionParser::_parseComparison(
const char* name,
ComparisonMatchExpression* cmp,
const BSONElement& e,
const CollatorInterface* collator) {
std::unique_ptr temp(cmp);
// Non-equality comparison match expressions cannot have
// a regular expression as the argument (e.g. {a: {$gt: /b/}} is illegal).
if (MatchExpression::EQ != cmp->matchType() && RegEx == e.type()) {
mongoutils::str::stream ss;
ss << "Can't have RegEx as arg to predicate over field '" << name << "'.";
return {Status(ErrorCodes::BadValue, ss)};
}
Status s = temp->init(name, e);
if (!s.isOK())
return s;
temp->setCollator(collator);
return {std::move(temp)};
}
StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& context,
const AndMatchExpression* andSoFar,
const char* name,
const BSONElement& e,
const CollatorInterface* collator,
int level) {
// TODO: these should move to getGtLtOp, or its replacement
if (mongoutils::str::equals("$eq", e.fieldName()))
return _parseComparison(name, new EqualityMatchExpression(), e, collator);
if (mongoutils::str::equals("$not", e.fieldName())) {
return _parseNot(name, e, collator, level);
}
int x = e.getGtLtOp(-1);
switch (x) {
case -1:
// $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")};
}
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "unknown operator: " << e.fieldName())};
case BSONObj::LT:
return _parseComparison(name, new LTMatchExpression(), e, collator);
case BSONObj::LTE:
return _parseComparison(name, new LTEMatchExpression(), e, collator);
case BSONObj::GT:
return _parseComparison(name, new GTMatchExpression(), e, collator);
case BSONObj::GTE:
return _parseComparison(name, new GTEMatchExpression(), e, collator);
case BSONObj::NE: {
if (RegEx == e.type()) {
// Just because $ne can be rewritten as the negation of an
// equality does not mean that $ne of a regex is allowed. See SERVER-1705.
return {Status(ErrorCodes::BadValue, "Can't have regex as arg to $ne.")};
}
StatusWithMatchExpression s =
_parseComparison(name, new EqualityMatchExpression(), e, collator);
if (!s.isOK())
return s;
std::unique_ptr n = stdx::make_unique();
Status s2 = n->init(s.getValue().release());
if (!s2.isOK())
return s2;
return {std::move(n)};
}
case BSONObj::Equality:
return _parseComparison(name, new EqualityMatchExpression(), e, collator);
case BSONObj::opIN: {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$in needs an array")};
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name);
if (!s.isOK())
return s;
s = _parseInExpression(temp.get(), e.Obj(), collator);
if (!s.isOK())
return s;
return {std::move(temp)};
}
case BSONObj::NIN: {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$nin needs an array")};
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name);
if (!s.isOK())
return s;
s = _parseInExpression(temp.get(), e.Obj(), collator);
if (!s.isOK())
return s;
std::unique_ptr temp2 = stdx::make_unique();
s = temp2->init(temp.release());
if (!s.isOK())
return s;
return {std::move(temp2)};
}
case BSONObj::opSIZE: {
int size = 0;
if (e.type() == NumberInt) {
size = e.numberInt();
} else if (e.type() == NumberLong) {
if (e.numberInt() == e.numberLong()) {
size = e.numberInt();
} else {
return {Status(ErrorCodes::BadValue,
"$size must be representable as a 32-bit integer")};
}
} else if (e.type() == NumberDouble) {
if (e.numberInt() == e.numberDouble()) {
size = e.numberInt();
} else {
return {Status(ErrorCodes::BadValue, "$size must be a whole number")};
}
} else {
return {Status(ErrorCodes::BadValue, "$size needs a number")};
}
if (size < 0) {
return {Status(ErrorCodes::BadValue, "$size may not be negative")};
}
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name, size);
if (!s.isOK())
return s;
return {std::move(temp)};
}
case BSONObj::opEXISTS: {
if (e.eoo())
return {Status(ErrorCodes::BadValue, "$exists can't be eoo")};
std::unique_ptr temp =
stdx::make_unique();
Status s = temp->init(name);
if (!s.isOK())
return s;
if (e.trueValue())
return {std::move(temp)};
std::unique_ptr temp2 = stdx::make_unique();
s = temp2->init(temp.release());
if (!s.isOK())
return s;
return {std::move(temp2)};
}
case BSONObj::opTYPE:
return _parseType(name, e);
case BSONObj::opMOD:
return _parseMOD(name, e);
case BSONObj::opOPTIONS: {
// TODO: try to optimize this
// we have to do this since $options can be before or after a $regex
// but we validate here
BSONObjIterator i(context);
while (i.more()) {
BSONElement temp = i.next();
if (temp.getGtLtOp(-1) == BSONObj::opREGEX)
return {nullptr};
}
return {Status(ErrorCodes::BadValue, "$options needs a $regex")};
}
case BSONObj::opREGEX: {
return _parseRegexDocument(name, context);
}
case BSONObj::opELEM_MATCH:
return _parseElemMatch(name, e, collator, level);
case BSONObj::opALL:
return _parseAll(name, e, collator, level);
case BSONObj::opWITHIN:
case BSONObj::opGEO_INTERSECTS:
return expressionParserGeoCallback(name, x, context);
case BSONObj::opNEAR:
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "near must be first in: " << context)};
// Handles bitwise query operators.
case BSONObj::opBITS_ALL_SET: {
return _parseBitTest(name, e);
}
case BSONObj::opBITS_ALL_CLEAR: {
return _parseBitTest(name, e);
}
case BSONObj::opBITS_ANY_SET: {
return _parseBitTest(name, e);
}
case BSONObj::opBITS_ANY_CLEAR: {
return _parseBitTest(name, e);
}
}
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "not handled: " << e.fieldName())};
}
StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj,
const CollatorInterface* collator,
int level) {
if (level > kMaximumTreeDepth) {
mongoutils::str::stream ss;
ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at "
<< obj.toString();
return {Status(ErrorCodes::BadValue, ss)};
}
std::unique_ptr root = stdx::make_unique();
bool topLevel = (level == 0);
level++;
BSONObjIterator i(obj);
while (i.more()) {
BSONElement e = i.next();
if (e.fieldName()[0] == '$') {
const char* rest = e.fieldName() + 1;
// TODO: optimize if block?
if (mongoutils::str::equals("or", rest)) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$or must be an array")};
std::unique_ptr temp = stdx::make_unique();
Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
if (!s.isOK())
return s;
root->add(temp.release());
} else if (mongoutils::str::equals("and", rest)) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$and must be an array")};
std::unique_ptr temp = stdx::make_unique();
Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
if (!s.isOK())
return s;
root->add(temp.release());
} else if (mongoutils::str::equals("nor", rest)) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$nor must be an array")};
std::unique_ptr temp = stdx::make_unique();
Status s = _parseTreeList(e.Obj(), temp.get(), collator, level);
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,
"$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)) {
StatusWithMatchExpression s = _extensionsCallback->parseWhere(e);
if (!s.isOK())
return s;
root->add(s.getValue().release());
} else if (mongoutils::str::equals("text", rest)) {
StatusWithMatchExpression s = _extensionsCallback->parseText(e);
if (!s.isOK()) {
return s;
}
root->add(s.getValue().release());
} else if (mongoutils::str::equals("comment", rest)) {
} else if (mongoutils::str::equals("ref", rest) ||
mongoutils::str::equals("id", rest) || mongoutils::str::equals("db", rest)) {
// DBRef fields.
std::unique_ptr eq =
stdx::make_unique();
Status s = eq->init(e.fieldName(), e);
if (!s.isOK())
return s;
// 'id' is collation-aware. 'ref' and 'db' are compared using binary comparison.
eq->setCollator(str::equals("id", rest) ? collator : nullptr);
root->add(eq.release());
} else {
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "unknown top level operator: "
<< e.fieldName())};
}
continue;
}
if (_isExpressionDocument(e, false)) {
Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), collator, level);
if (!s.isOK())
return s;
continue;
}
if (e.type() == RegEx) {
StatusWithMatchExpression result = _parseRegexElement(e.fieldName(), e);
if (!result.isOK())
return result;
root->add(result.getValue().release());
continue;
}
std::unique_ptr eq =
stdx::make_unique();
Status s = eq->init(e.fieldName(), e);
if (!s.isOK())
return s;
eq->setCollator(collator);
root->add(eq.release());
}
if (root->numChildren() == 1) {
std::unique_ptr real(root->getChild(0));
root->clearAndRelease();
return {std::move(real)};
}
return {std::move(root)};
}
Status MatchExpressionParser::_parseSub(const char* name,
const BSONObj& sub,
AndMatchExpression* root,
const CollatorInterface* collator,
int level) {
// 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
// its subfields and parse them one at a time (there is no $maxDistance without $near), so
// we hand the entire object over to the geo parsing routines.
if (level > kMaximumTreeDepth) {
mongoutils::str::stream ss;
ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at "
<< sub.toString();
return Status(ErrorCodes::BadValue, ss);
}
level++;
// Special case parsing for geoNear. This is necessary in order to support query formats like
// {$near: , $maxDistance: }. No other query operators allow $-prefixed
// modifiers as sibling BSON elements.
BSONObjIterator geoIt(sub);
if (geoIt.more()) {
BSONElement firstElt = geoIt.next();
if (firstElt.isABSONObj()) {
const char* fieldName = firstElt.fieldName();
// TODO: Having these $fields here isn't ideal but we don't want to pull in anything
// from db/geo at this point, since it may not actually be linked in...
if (mongoutils::str::equals(fieldName, "$near") ||
mongoutils::str::equals(fieldName, "$nearSphere") ||
mongoutils::str::equals(fieldName, "$geoNear")) {
StatusWithMatchExpression s =
expressionParserGeoCallback(name, firstElt.getGtLtOp(), sub);
if (s.isOK()) {
root->add(s.getValue().release());
}
// Propagate geo parsing result to caller.
return s.getStatus();
}
}
}
BSONObjIterator j(sub);
while (j.more()) {
BSONElement deep = j.next();
StatusWithMatchExpression s = _parseSubField(sub, root, name, deep, collator, level);
if (!s.isOK())
return s.getStatus();
if (s.getValue())
root->add(s.getValue().release());
}
return Status::OK();
}
bool MatchExpressionParser::_isExpressionDocument(const BSONElement& e, bool allowIncompleteDBRef) {
if (e.type() != Object)
return false;
BSONObj o = e.Obj();
if (o.isEmpty())
return false;
const char* name = o.firstElement().fieldName();
if (name[0] != '$')
return false;
if (_isDBRefDocument(o, allowIncompleteDBRef)) {
return false;
}
return true;
}
/**
* DBRef fields are ordered in the collection.
* In the query, we consider an embedded object a query on
* a DBRef as long as it contains $ref and $id.
* Required fields: $ref and $id (if incomplete DBRefs are not allowed)
*
* If incomplete DBRefs are allowed, we accept the BSON object as long as it
* contains $ref, $id or $db.
*
* Field names are checked but not field types.
*/
bool MatchExpressionParser::_isDBRefDocument(const BSONObj& obj, bool allowIncompleteDBRef) {
bool hasRef = false;
bool hasID = false;
bool hasDB = false;
BSONObjIterator i(obj);
while (i.more() && !(hasRef && hasID)) {
BSONElement element = i.next();
const char* fieldName = element.fieldName();
// $ref
if (!hasRef && mongoutils::str::equals("$ref", fieldName)) {
hasRef = true;
}
// $id
else if (!hasID && mongoutils::str::equals("$id", fieldName)) {
hasID = true;
}
// $db
else if (!hasDB && mongoutils::str::equals("$db", fieldName)) {
hasDB = true;
}
}
if (allowIncompleteDBRef) {
return hasRef || hasID || hasDB;
}
return hasRef && hasID;
}
StatusWithMatchExpression MatchExpressionParser::_parseMOD(const char* name, const BSONElement& e) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "malformed mod, needs to be an array")};
BSONObjIterator i(e.Obj());
if (!i.more())
return {Status(ErrorCodes::BadValue, "malformed mod, not enough elements")};
BSONElement d = i.next();
if (!d.isNumber())
return {Status(ErrorCodes::BadValue, "malformed mod, divisor not a number")};
if (!i.more())
return {Status(ErrorCodes::BadValue, "malformed mod, not enough elements")};
BSONElement r = i.next();
if (!d.isNumber())
return {Status(ErrorCodes::BadValue, "malformed mod, remainder not a number")};
if (i.more())
return {Status(ErrorCodes::BadValue, "malformed mod, too many elements")};
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name, d.numberInt(), r.numberInt());
if (!s.isOK())
return s;
return {std::move(temp)};
}
StatusWithMatchExpression MatchExpressionParser::_parseRegexElement(const char* name,
const BSONElement& e) {
if (e.type() != RegEx)
return {Status(ErrorCodes::BadValue, "not a regex")};
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name, e.regex(), e.regexFlags());
if (!s.isOK())
return s;
return {std::move(temp)};
}
StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument(const char* name,
const BSONObj& doc) {
string regex;
string regexOptions;
BSONObjIterator i(doc);
while (i.more()) {
BSONElement e = i.next();
switch (e.getGtLtOp()) {
case BSONObj::opREGEX:
if (e.type() == String) {
regex = e.String();
} else if (e.type() == RegEx) {
regex = e.regex();
regexOptions = e.regexFlags();
} else {
return {Status(ErrorCodes::BadValue, "$regex has to be a string")};
}
break;
case BSONObj::opOPTIONS:
if (e.type() != String)
return {Status(ErrorCodes::BadValue, "$options has to be a string")};
regexOptions = e.String();
break;
default:
break;
}
}
std::unique_ptr temp = stdx::make_unique();
Status s = temp->init(name, regex, regexOptions);
if (!s.isOK())
return s;
return {std::move(temp)};
}
Status MatchExpressionParser::_parseInExpression(InMatchExpression* inExpression,
const BSONObj& theArray,
const CollatorInterface* collator) {
inExpression->setCollator(collator);
BSONObjIterator i(theArray);
while (i.more()) {
BSONElement e = i.next();
// Allow DBRefs, but reject all fields with names starting with $.
if (_isExpressionDocument(e, false)) {
return Status(ErrorCodes::BadValue, "cannot nest $ under $in");
}
if (e.type() == RegEx) {
std::unique_ptr r = stdx::make_unique();
Status s = r->init("", e);
if (!s.isOK())
return s;
s = inExpression->addRegex(std::move(r));
if (!s.isOK())
return s;
} else {
Status s = inExpression->addEquality(e);
if (!s.isOK())
return s;
}
}
return Status::OK();
}
StatusWithMatchExpression MatchExpressionParser::_parseType(const char* name,
const BSONElement& elt) {
if (!elt.isNumber() && elt.type() != BSONType::String) {
return Status(ErrorCodes::TypeMismatch, "argument to $type is not a number or a string");
}
std::unique_ptr temp = stdx::make_unique();
int typeInt;
// The element can be a number (the BSON type number) or a string representing the name
// of the type.
if (elt.isNumber()) {
typeInt = elt.numberInt();
if (elt.type() != NumberInt && typeInt != elt.number()) {
typeInt = -1;
}
} else {
invariant(elt.type() == BSONType::String);
std::string typeAlias = elt.str();
// If typeAlias is 'number', initialize as matching against all number types.
if (typeAlias == TypeMatchExpression::kMatchesAllNumbersAlias) {
Status s = temp->initAsMatchingAllNumbers(name);
if (!s.isOK()) {
return s;
}
return {std::move(temp)};
}
// Search the string-int map for the typeAlias (case-sensitive).
std::unordered_map::const_iterator it =
TypeMatchExpression::typeAliasMap.find(typeAlias);
if (it == TypeMatchExpression::typeAliasMap.end()) {
std::stringstream ss;
ss << "unknown string alias for $type: " << typeAlias;
return Status(ErrorCodes::BadValue, ss.str());
}
typeInt = it->second;
}
Status s = temp->initWithBSONType(name, typeInt);
if (!s.isOK()) {
return s;
}
return {std::move(temp)};
}
StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
int level) {
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.
bool isElemMatchValue = false;
if (_isExpressionDocument(e, true)) {
BSONObj o = e.Obj();
BSONElement elt = o.firstElement();
invariant(!elt.eoo());
isElemMatchValue = !mongoutils::str::equals("$and", elt.fieldName()) &&
!mongoutils::str::equals("$nor", elt.fieldName()) &&
!mongoutils::str::equals("$or", elt.fieldName()) &&
!mongoutils::str::equals("$where", elt.fieldName());
}
if (isElemMatchValue) {
// value case
AndMatchExpression theAnd;
Status s = _parseSub("", obj, &theAnd, collator, level);
if (!s.isOK())
return s;
std::unique_ptr temp =
stdx::make_unique();
s = temp->init(name);
if (!s.isOK())
return s;
for (size_t i = 0; i < theAnd.numChildren(); i++) {
temp->add(theAnd.getChild(i));
}
theAnd.clearAndRelease();
return {std::move(temp)};
}
// DBRef value case
// A DBRef document under a $elemMatch should be treated as an object case
// because it may contain non-DBRef fields in addition to $ref, $id and $db.
// object case
StatusWithMatchExpression subRaw = _parse(obj, collator, level);
if (!subRaw.isOK())
return subRaw;
std::unique_ptr 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 temp =
stdx::make_unique();
Status status = temp->init(name, sub.release());
if (!status.isOK())
return status;
return {std::move(temp)};
}
StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name,
const BSONElement& e,
const CollatorInterface* collator,
int level) {
if (e.type() != Array)
return {Status(ErrorCodes::BadValue, "$all needs an array")};
BSONObj arr = e.Obj();
std::unique_ptr myAnd = stdx::make_unique();
BSONObjIterator i(arr);
if (arr.firstElement().type() == Object &&
mongoutils::str::equals("$elemMatch",
arr.firstElement().Obj().firstElement().fieldName())) {
// $all : [ { $elemMatch : {} } ... ]
while (i.more()) {
BSONElement hopefullyElemMatchElement = i.next();
if (hopefullyElemMatchElement.type() != Object) {
// $all : [ { $elemMatch : ... }, 5 ]
return {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")};
}
BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj();
if (!mongoutils::str::equals("$elemMatch",
hopefullyElemMatchObj.firstElement().fieldName())) {
// $all : [ { $elemMatch : ... }, { x : 5 } ]
return {Status(ErrorCodes::BadValue, "$all/$elemMatch has to be consistent")};
}
StatusWithMatchExpression inner =
_parseElemMatch(name, hopefullyElemMatchObj.firstElement(), collator, level);
if (!inner.isOK())
return inner;
myAnd->add(inner.getValue().release());
}
return {std::move(myAnd)};
}
while (i.more()) {
BSONElement e = i.next();
if (e.type() == RegEx) {
std::unique_ptr r = stdx::make_unique();
Status s = r->init(name, e);
if (!s.isOK())
return s;
myAnd->add(r.release());
} else if (e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1) {
return {Status(ErrorCodes::BadValue, "no $ expressions in $all")};
} else {
std::unique_ptr x =
stdx::make_unique();
Status s = x->init(name, e);
if (!s.isOK())
return s;
x->setCollator(collator);
myAnd->add(x.release());
}
}
if (myAnd->numChildren() == 0) {
return {stdx::make_unique(name)};
}
return {std::move(myAnd)};
}
template
StatusWithMatchExpression MatchExpressionParser::_parseBitTest(const char* name,
const BSONElement& e) {
std::unique_ptr bitTestMatchExpression = stdx::make_unique();
if (e.type() == BSONType::Array) {
// Array of bit positions provided as value.
auto statusWithBitPositions = _parseBitPositionsArray(e.Obj());
if (!statusWithBitPositions.isOK()) {
return statusWithBitPositions.getStatus();
}
std::vector bitPositions = statusWithBitPositions.getValue();
Status s = bitTestMatchExpression->init(name, bitPositions);
if (!s.isOK()) {
return s;
}
} else if (e.isNumber()) {
// Integer bitmask provided as value.
if (e.type() == BSONType::NumberDouble) {
double eDouble = e.numberDouble();
// NaN doubles are rejected.
if (std::isnan(eDouble)) {
mongoutils::str::stream ss;
ss << name << " cannot take a NaN";
return Status(ErrorCodes::BadValue, ss);
}
// No integral doubles that are too large to be represented as a 64 bit signed integer.
// We use 'kLongLongMaxAsDouble' because if we just did eDouble > 2^63-1, it would be
// compared against 2^63. eDouble=2^63 would not get caught that way.
if (eDouble >= BitTestMatchExpression::kLongLongMaxPlusOneAsDouble ||
eDouble < std::numeric_limits::min()) {
mongoutils::str::stream ss;
ss << name << " cannot be represented as a 64-bit integer: " << e;
return Status(ErrorCodes::BadValue, ss);
}
// This checks if e is an integral double.
if (eDouble != static_cast(static_cast(eDouble))) {
mongoutils::str::stream ss;
ss << name << " cannot have a fractional part but received: " << e;
return Status(ErrorCodes::BadValue, ss);
}
}
long long bitMask = e.numberLong();
// No negatives.
if (bitMask < 0) {
mongoutils::str::stream ss;
ss << name << " cannot take a negative number: " << e;
return Status(ErrorCodes::BadValue, ss);
}
Status s = bitTestMatchExpression->init(name, bitMask);
if (!s.isOK()) {
return s;
}
} else if (e.type() == BSONType::BinData) {
// Binary bitmask provided as value.
int eBinaryLen;
const char* eBinary = e.binData(eBinaryLen);
Status s = bitTestMatchExpression->init(name, eBinary, eBinaryLen);
if (!s.isOK()) {
return s;
}
} else {
mongoutils::str::stream ss;
ss << name << " takes an Array, a number, or a BinData but received: " << e;
return Status(ErrorCodes::BadValue, ss);
}
return {std::move(bitTestMatchExpression)};
}
StatusWith> MatchExpressionParser::_parseBitPositionsArray(
const BSONObj& theArray) {
std::vector bitPositions;
// Fill temporary bit position array with integers read from the BSON array.
for (const BSONElement& e : theArray) {
if (!e.isNumber()) {
mongoutils::str::stream ss;
ss << "bit positions must be an integer but got: " << e;
return Status(ErrorCodes::BadValue, ss);
}
if (e.type() == BSONType::NumberDouble) {
double eDouble = e.numberDouble();
// NaN doubles are rejected.
if (std::isnan(eDouble)) {
mongoutils::str::stream ss;
ss << "bit positions cannot take a NaN: " << e;
return Status(ErrorCodes::BadValue, ss);
}
// This makes sure e does not overflow a 32-bit integer container.
if (eDouble > std::numeric_limits::max() ||
eDouble < std::numeric_limits::min()) {
mongoutils::str::stream ss;
ss << "bit positions cannot be represented as a 32-bit signed integer: " << e;
return Status(ErrorCodes::BadValue, ss);
}
// This checks if e is integral.
if (eDouble != static_cast(static_cast(eDouble))) {
mongoutils::str::stream ss;
ss << "bit positions must be an integer but got: " << e;
return Status(ErrorCodes::BadValue, ss);
}
}
if (e.type() == BSONType::NumberLong) {
long long eLong = e.numberLong();
// This makes sure e does not overflow a 32-bit integer container.
if (eLong > std::numeric_limits::max() ||
eLong < std::numeric_limits::min()) {
mongoutils::str::stream ss;
ss << "bit positions cannot be represented as a 32-bit signed integer: " << e;
return Status(ErrorCodes::BadValue, ss);
}
}
int eValue = e.numberInt();
// No negatives.
if (eValue < 0) {
mongoutils::str::stream ss;
ss << "bit positions must be >= 0 but got: " << e;
return Status(ErrorCodes::BadValue, ss);
}
bitPositions.push_back(eValue);
}
return bitPositions;
}
// Geo
StatusWithMatchExpression expressionParserGeoCallbackDefault(const char* name,
int type,
const BSONObj& section) {
return {Status(ErrorCodes::BadValue, "geo not linked in")};
}
MatchExpressionParserGeoCallback expressionParserGeoCallback = expressionParserGeoCallbackDefault;
}