summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/expression.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/expression.cpp')
-rw-r--r--src/mongo/db/pipeline/expression.cpp2411
1 files changed, 1043 insertions, 1368 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index d4a26ce78e4..82d5c69cbea 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -12,28 +12,155 @@
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 "pch.h"
+
#include "db/pipeline/expression.h"
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/preprocessor/cat.hpp> // like the ## operator but works with __LINE__
#include <cstdio>
-#include "db/jsobj.h"
-#include "db/pipeline/builder.h"
-#include "db/pipeline/document.h"
-#include "db/pipeline/expression_context.h"
-#include "db/pipeline/value.h"
-#include "util/mongoutils/str.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/pipeline/document.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/pipeline/value.h"
+#include "mongo/util/string_map.h"
+#include "mongo/util/mongoutils/str.h"
namespace mongo {
using namespace mongoutils;
- /* --------------------------- Expression ------------------------------ */
+ /// Helper function to easily wrap constants with $const.
+ static Value serializeConstant(Value val) {
+ return Value(DOC("$const" << val));
+ }
+
+ void Variables::uassertValidNameForUserWrite(StringData varName) {
+ // System variables users allowed to write to (currently just one)
+ if (varName == "CURRENT") {
+ return;
+ }
+
+ uassert(16866, "empty variable names are not allowed",
+ !varName.empty());
+
+ const bool firstCharIsValid = (varName[0] >= 'a' && varName[0] <= 'z')
+ || (varName[0] & '\x80') // non-ascii
+ ;
+
+ uassert(16867, str::stream() <<
+ "'" << varName << "' starts with an invalid character for a user variable name",
+ firstCharIsValid);
+
+ for (size_t i = 1; i < varName.size(); i++) {
+ const bool charIsValid = (varName[i] >= 'a' && varName[i] <= 'z')
+ || (varName[i] >= 'A' && varName[i] <= 'Z')
+ || (varName[i] >= '0' && varName[i] <= '9')
+ || (varName[i] == '_')
+ || (varName[i] & '\x80') // non-ascii
+ ;
+
+ uassert(16868, str::stream() << "'" << varName << "' contains an invalid character "
+ << "for a variable name: '" << varName[i] << "'",
+ charIsValid);
+ }
+ }
+
+ void Variables::uassertValidNameForUserRead(StringData varName) {
+ uassert(16869, "empty variable names are not allowed",
+ !varName.empty());
+
+ const bool firstCharIsValid = (varName[0] >= 'a' && varName[0] <= 'z')
+ || (varName[0] >= 'A' && varName[0] <= 'Z')
+ || (varName[0] & '\x80') // non-ascii
+ ;
+
+ uassert(16870, str::stream() <<
+ "'" << varName << "' starts with an invalid character for a variable name",
+ firstCharIsValid);
+
+ for (size_t i = 1; i < varName.size(); i++) {
+ const bool charIsValid = (varName[i] >= 'a' && varName[i] <= 'z')
+ || (varName[i] >= 'A' && varName[i] <= 'Z')
+ || (varName[i] >= '0' && varName[i] <= '9')
+ || (varName[i] == '_')
+ || (varName[i] & '\x80') // non-ascii
+ ;
+
+ uassert(16871, str::stream() << "'" << varName << "' contains an invalid character "
+ << "for a variable name: '" << varName[i] << "'",
+ charIsValid);
+ }
+ }
+
+ void Variables::setValue(Id id, const Value& value) {
+ massert(17199, "can't use Variables::setValue to set ROOT",
+ id != ROOT_ID);
+
+ verify(id < _numVars);
+ _rest[id] = value;
+ }
+
+ Value Variables::getValue(Id id) const {
+ if (id == ROOT_ID)
+ return Value(_root);
+
+ verify(id < _numVars);
+ return _rest[id];
+ }
+
+ Document Variables::getDocument(Id id) const {
+ if (id == ROOT_ID)
+ return _root;
+
+ verify(id < _numVars);
+ const Value var = _rest[id];
+ if (var.getType() == Object)
+ return var.getDocument();
+
+ return Document();
+ }
+
+ Variables::Id VariablesParseState::defineVariable(const StringData& name) {
+ // caller should have validated before hand by using Variables::uassertValidNameForUserWrite
+ massert(17275, "Can't redefine ROOT",
+ name != "ROOT");
+
+ Variables::Id id = _idGenerator->generateId();
+ _variables[name] = id;
+ return id;
+ }
+
+ Variables::Id VariablesParseState::getVariable(const StringData& name) const {
+ StringMap<Variables::Id>::const_iterator it = _variables.find(name);
+ if (it != _variables.end())
+ return it->second;
+
+ uassert(17276, str::stream() << "Use of undefined variable: " << name,
+ name == "ROOT" || name == "CURRENT");
- void Expression::toMatcherBson(BSONObjBuilder *pBuilder) const {
- verify(false && "Expression::toMatcherBson()");
+ return Variables::ROOT_ID;
}
+ /* --------------------------- Expression ------------------------------ */
+
Expression::ObjectCtx::ObjectCtx(int theOptions)
: options(theOptions)
{}
@@ -63,7 +190,9 @@ namespace mongo {
}
intrusive_ptr<Expression> Expression::parseObject(
- BSONElement *pBsonElement, ObjectCtx *pCtx) {
+ BSONObj obj,
+ ObjectCtx* pCtx,
+ const VariablesParseState& vps) {
/*
An object expression can take any of the following forms:
@@ -75,7 +204,6 @@ namespace mongo {
intrusive_ptr<ExpressionObject> pExpressionObject; // alt result
enum { UNKNOWN, NOTOPERATOR, OPERATOR } kind = UNKNOWN;
- BSONObj obj(pBsonElement->Obj());
if (obj.isEmpty())
return ExpressionObject::create();
BSONObjIterator iter(obj);
@@ -96,7 +224,7 @@ namespace mongo {
/* we've determined this "object" is an operator expression */
kind = OPERATOR;
- pExpression = parseExpression(pFieldName, &fieldElement);
+ pExpression = parseExpression(fieldElement, vps);
}
else {
uassert(15990, str::stream() << "this object is already an operator expression, and can't be used as a document expression (at '" <<
@@ -111,7 +239,8 @@ namespace mongo {
verify(pCtx->documentOk());
// CW TODO error: document not allowed in this context
- pExpressionObject = ExpressionObject::create();
+ pExpressionObject = pCtx->topLevel() ? ExpressionObject::createRoot()
+ : ExpressionObject::create();
pExpression = pExpressionObject;
/* this "object" is not an operator expression */
@@ -126,18 +255,17 @@ namespace mongo {
ObjectCtx oCtx(
(pCtx->documentOk() ? ObjectCtx::DOCUMENT_OK : 0)
| (pCtx->inclusionOk() ? ObjectCtx::INCLUSION_OK : 0));
- intrusive_ptr<Expression> pNested(
- parseObject(&fieldElement, &oCtx));
- pExpressionObject->addField(fieldName, pNested);
+
+ pExpressionObject->addField(fieldName,
+ parseObject(fieldElement.Obj(), &oCtx, vps));
break;
}
case String: {
/* it's a renamed field */
// CW TODO could also be a constant
- intrusive_ptr<Expression> pPath(
- ExpressionFieldPath::create(
- removeFieldPrefix(fieldElement.str())));
- pExpressionObject->addField(fieldName, pPath);
+ pExpressionObject->addField(fieldName,
+ ExpressionFieldPath::parse(fieldElement.str(),
+ vps));
break;
}
case Bool:
@@ -170,165 +298,82 @@ namespace mongo {
return pExpression;
}
+namespace {
+ typedef boost::function<intrusive_ptr<Expression>(BSONElement, const VariablesParseState&)>
+ ExpressionParser;
+ StringMap<ExpressionParser> expressionParserMap;
+}
- struct OpDesc {
- const char *pName;
- intrusive_ptr<ExpressionNary> (*pFactory)(void);
-
- unsigned flag;
- static const unsigned FIXED_COUNT = 0x0001;
- static const unsigned OBJECT_ARG = 0x0002;
-
- unsigned argCount;
- };
-
- static int OpDescCmp(const void *pL, const void *pR) {
- return strcmp(((const OpDesc *)pL)->pName, ((const OpDesc *)pR)->pName);
- }
-
- /*
- Keep these sorted alphabetically so we can bsearch() them using
- OpDescCmp() above.
- */
- static const OpDesc OpTable[] = {
- {"$add", ExpressionAdd::create, 0},
- {"$and", ExpressionAnd::create, 0},
- {"$cmp", ExpressionCompare::createCmp, OpDesc::FIXED_COUNT, 2},
- {"$concat", ExpressionConcat::create, 0},
- {"$cond", ExpressionCond::create, OpDesc::FIXED_COUNT, 3},
- // $const handled specially in parseExpression
- {"$dayOfMonth", ExpressionDayOfMonth::create, OpDesc::FIXED_COUNT, 1},
- {"$dayOfWeek", ExpressionDayOfWeek::create, OpDesc::FIXED_COUNT, 1},
- {"$dayOfYear", ExpressionDayOfYear::create, OpDesc::FIXED_COUNT, 1},
- {"$divide", ExpressionDivide::create, OpDesc::FIXED_COUNT, 2},
- {"$eq", ExpressionCompare::createEq, OpDesc::FIXED_COUNT, 2},
- {"$gt", ExpressionCompare::createGt, OpDesc::FIXED_COUNT, 2},
- {"$gte", ExpressionCompare::createGte, OpDesc::FIXED_COUNT, 2},
- {"$hour", ExpressionHour::create, OpDesc::FIXED_COUNT, 1},
- {"$ifNull", ExpressionIfNull::create, OpDesc::FIXED_COUNT, 2},
- {"$lt", ExpressionCompare::createLt, OpDesc::FIXED_COUNT, 2},
- {"$lte", ExpressionCompare::createLte, OpDesc::FIXED_COUNT, 2},
- {"$millisecond", ExpressionMillisecond::create, OpDesc::FIXED_COUNT, 1},
- {"$minute", ExpressionMinute::create, OpDesc::FIXED_COUNT, 1},
- {"$mod", ExpressionMod::create, OpDesc::FIXED_COUNT, 2},
- {"$month", ExpressionMonth::create, OpDesc::FIXED_COUNT, 1},
- {"$multiply", ExpressionMultiply::create, 0},
- {"$ne", ExpressionCompare::createNe, OpDesc::FIXED_COUNT, 2},
- {"$not", ExpressionNot::create, OpDesc::FIXED_COUNT, 1},
- {"$or", ExpressionOr::create, 0},
- {"$second", ExpressionSecond::create, OpDesc::FIXED_COUNT, 1},
- {"$strcasecmp", ExpressionStrcasecmp::create, OpDesc::FIXED_COUNT, 2},
- {"$substr", ExpressionSubstr::create, OpDesc::FIXED_COUNT, 3},
- {"$subtract", ExpressionSubtract::create, OpDesc::FIXED_COUNT, 2},
- {"$toLower", ExpressionToLower::create, OpDesc::FIXED_COUNT, 1},
- {"$toUpper", ExpressionToUpper::create, OpDesc::FIXED_COUNT, 1},
- {"$week", ExpressionWeek::create, OpDesc::FIXED_COUNT, 1},
- {"$year", ExpressionYear::create, OpDesc::FIXED_COUNT, 1},
- };
-
- static const size_t NOp = sizeof(OpTable)/sizeof(OpTable[0]);
+/** Registers an ExpressionParser so it can be called from parseExpression and friends.
+ *
+ * As an example, if your expression looks like {"$foo": [1,2,3]} you would add this line:
+ * REGISTER_EXPRESSION("$foo", ExpressionFoo::parse);
+ */
+#define REGISTER_EXPRESSION(key, parserFunc) \
+ MONGO_INITIALIZER(BOOST_PP_CAT(addToExpressionParserMap, __LINE__))(InitializerContext*) { \
+ /* prevent duplicate expressions */ \
+ StringMap<ExpressionParser>::const_iterator op = expressionParserMap.find(key); \
+ massert(17064, str::stream() << "Duplicate expression (" << key << ") detected at " \
+ << __FILE__ << ":" << __LINE__, \
+ op == expressionParserMap.end()); \
+ /* register expression */ \
+ expressionParserMap[key] = (parserFunc); \
+ return Status::OK(); \
+ }
intrusive_ptr<Expression> Expression::parseExpression(
- const char *pOpName, BSONElement *pBsonElement) {
- /* look for the specified operator */
+ BSONElement exprElement,
+ const VariablesParseState& vps) {
- if (str::equals(pOpName, "$const")) {
- return ExpressionConstant::createFromBsonElement(pBsonElement);
- }
-
- OpDesc key;
- key.pName = pOpName;
- const OpDesc *pOp = (const OpDesc *)bsearch(
- &key, OpTable, NOp, sizeof(OpDesc), OpDescCmp);
-
- uassert(15999, str::stream() << "invalid operator '" <<
- pOpName << "'", pOp);
+ /* look for the specified operator */
+ const char* opName = exprElement.fieldName();
+ StringMap<ExpressionParser>::const_iterator op = expressionParserMap.find(opName);
+ uassert(15999, str::stream() << "invalid operator '" << opName << "'",
+ op != expressionParserMap.end());
/* make the expression node */
- intrusive_ptr<ExpressionNary> pExpression((*pOp->pFactory)());
-
- /* add the operands to the expression node */
- BSONType elementType = pBsonElement->type();
-
- if (pOp->flag & OpDesc::FIXED_COUNT) {
- if (pOp->argCount > 1)
- uassert(16019, str::stream() << "the " << pOp->pName <<
- " operator requires an array of " << pOp->argCount <<
- " operands", elementType == Array);
- }
+ return op->second(exprElement, vps);
+ }
- if (elementType == Object) {
- /* the operator must be unary and accept an object argument */
- uassert(16021, str::stream() << "the " << pOp->pName <<
- " operator does not accept an object as an operand",
- pOp->flag & OpDesc::OBJECT_ARG);
+ Expression::ExpressionVector ExpressionNary::parseArguments(
+ BSONElement exprElement,
+ const VariablesParseState& vps) {
- BSONObj objOperand(pBsonElement->Obj());
- ObjectCtx oCtx(ObjectCtx::DOCUMENT_OK);
- intrusive_ptr<Expression> pOperand(
- Expression::parseObject(pBsonElement, &oCtx));
- pExpression->addOperand(pOperand);
- }
- else if (elementType == Array) {
- /* multiple operands - an n-ary operator */
- vector<BSONElement> bsonArray(pBsonElement->Array());
- const size_t n = bsonArray.size();
-
- if (pOp->flag & OpDesc::FIXED_COUNT)
- uassert(16020, str::stream() << "the " << pOp->pName <<
- " operator requires " << pOp->argCount <<
- " operand(s)", pOp->argCount == n);
-
- for(size_t i = 0; i < n; ++i) {
- BSONElement *pBsonOperand = &bsonArray[i];
- intrusive_ptr<Expression> pOperand(
- Expression::parseOperand(pBsonOperand));
- pExpression->addOperand(pOperand);
+ ExpressionVector out;
+ if (exprElement.type() == Array) {
+ BSONForEach(elem, exprElement.Obj()) {
+ out.push_back(Expression::parseOperand(elem, vps));
}
}
- else {
- /* assume it's an atomic operand */
- if (pOp->flag & OpDesc::FIXED_COUNT)
- uassert(16022, str::stream() << "the " << pOp->pName <<
- " operator requires an array of " << pOp->argCount <<
- " operands", pOp->argCount == 1);
-
- intrusive_ptr<Expression> pOperand(
- Expression::parseOperand(pBsonElement));
- pExpression->addOperand(pOperand);
+ else { // assume it's an atomic operand
+ out.push_back(Expression::parseOperand(exprElement, vps));
}
- return pExpression;
+ return out;
}
- intrusive_ptr<Expression> Expression::parseOperand(BSONElement *pBsonElement) {
- BSONType type = pBsonElement->type();
+ intrusive_ptr<Expression> Expression::parseOperand(
+ BSONElement exprElement,
+ const VariablesParseState& vps) {
- if (type == String && pBsonElement->valuestr()[0] == '$') {
+ BSONType type = exprElement.type();
+
+ if (type == String && exprElement.valuestr()[0] == '$') {
/* if we got here, this is a field path expression */
- string fieldPath = removeFieldPrefix(pBsonElement->str());
- return ExpressionFieldPath::create(fieldPath);
+ return ExpressionFieldPath::parse(exprElement.str(), vps);
}
else if (type == Object) {
ObjectCtx oCtx(ObjectCtx::DOCUMENT_OK);
- return Expression::parseObject(pBsonElement, &oCtx);
+ return Expression::parseObject(exprElement.Obj(), &oCtx, vps);
}
else {
- return ExpressionConstant::createFromBsonElement(pBsonElement);
+ return ExpressionConstant::parse(exprElement, vps);
}
}
/* ------------------------- ExpressionAdd ----------------------------- */
- ExpressionAdd::~ExpressionAdd() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionAdd::create() {
- intrusive_ptr<ExpressionAdd> pExpression(new ExpressionAdd());
- return pExpression;
- }
-
- Value ExpressionAdd::evaluate(const Document& pDocument) const {
+ Value ExpressionAdd::evaluateInternal(Variables* vars) const {
/*
We'll try to return the narrowest possible result value. To do that
@@ -343,7 +388,7 @@ namespace mongo {
const size_t n = vpOperand.size();
for (size_t i = 0; i < n; ++i) {
- Value val = vpOperand[i]->evaluate(pDocument);
+ Value val = vpOperand[i]->evaluateInternal(vars);
if (val.numeric()) {
totalType = Value::getWidestNumeric(totalType, val.getType());
@@ -373,13 +418,13 @@ namespace mongo {
if (haveDate) {
if (totalType == NumberDouble)
longTotal = static_cast<long long>(doubleTotal);
- return Value::createDate(longTotal);
+ return Value(Date_t(longTotal));
}
else if (totalType == NumberLong) {
- return Value::createLong(longTotal);
+ return Value(longTotal);
}
else if (totalType == NumberDouble) {
- return Value::createDouble(doubleTotal);
+ return Value(doubleTotal);
}
else if (totalType == NumberInt) {
return Value::createIntOrLong(longTotal);
@@ -389,27 +434,33 @@ namespace mongo {
}
}
+ REGISTER_EXPRESSION("$add", ExpressionAdd::parse);
const char *ExpressionAdd::getOpName() const {
return "$add";
}
- intrusive_ptr<ExpressionNary> (*ExpressionAdd::getFactory() const)() {
- return ExpressionAdd::create;
- }
+ /* ------------------------- ExpressionAllElementsTrue -------------------------- */
- /* ------------------------- ExpressionAnd ----------------------------- */
-
- ExpressionAnd::~ExpressionAnd() {
+ Value ExpressionAllElementsTrue::evaluateInternal(Variables* vars) const {
+ const Value arr = vpOperand[0]->evaluateInternal(vars);
+ uassert(17040, str::stream() << getOpName() << "'s argument must be an array, but is "
+ << typeName(arr.getType()),
+ arr.getType() == Array);
+ const vector<Value>& array = arr.getArray();
+ for (vector<Value>::const_iterator it = array.begin(); it != array.end(); ++it) {
+ if (!it->coerceToBool()) {
+ return Value(false);
+ }
+ }
+ return Value(true);
}
- intrusive_ptr<ExpressionNary> ExpressionAnd::create() {
- intrusive_ptr<ExpressionNary> pExpression(new ExpressionAnd());
- return pExpression;
+ REGISTER_EXPRESSION("$allElementsTrue", ExpressionAllElementsTrue::parse);
+ const char *ExpressionAllElementsTrue::getOpName() const {
+ return "$allElementsTrue";
}
- ExpressionAnd::ExpressionAnd():
- ExpressionNary() {
- }
+ /* ------------------------- ExpressionAnd ----------------------------- */
intrusive_ptr<Expression> ExpressionAnd::optimize() {
/* optimize the conjunction as much as possible */
@@ -438,7 +489,7 @@ namespace mongo {
Evaluate and coerce the last argument to a boolean. If it's false,
then we can replace this entire expression.
*/
- bool last = pLast->evaluate(Document()).coerceToBool();
+ bool last = pConst->getValue().coerceToBool();
if (!last) {
intrusive_ptr<ExpressionConstant> pFinal(
ExpressionConstant::create(Value(false)));
@@ -468,10 +519,10 @@ namespace mongo {
return pE;
}
- Value ExpressionAnd::evaluate(const Document& pDocument) const {
+ Value ExpressionAnd::evaluateInternal(Variables* vars) const {
const size_t n = vpOperand.size();
for(size_t i = 0; i < n; ++i) {
- Value pValue(vpOperand[i]->evaluate(pDocument));
+ Value pValue(vpOperand[i]->evaluateInternal(vars));
if (!pValue.coerceToBool())
return Value(false);
}
@@ -479,31 +530,34 @@ namespace mongo {
return Value(true);
}
+ REGISTER_EXPRESSION("$and", ExpressionAnd::parse);
const char *ExpressionAnd::getOpName() const {
return "$and";
}
- void ExpressionAnd::toMatcherBson(BSONObjBuilder *pBuilder) const {
- /*
- There are two patterns we can handle:
- (1) one or two comparisons on the same field: { a:{$gte:3, $lt:7} }
- (2) multiple field comparisons: {a:7, b:{$lte:6}, c:2}
- This can be recognized as a conjunction of a set of range
- expressions. Direct equality is a degenerate range expression;
- range expressions can be open-ended.
- */
- verify(false && "unimplemented");
+ /* ------------------------- ExpressionAnyElementTrue -------------------------- */
+
+ Value ExpressionAnyElementTrue::evaluateInternal(Variables* vars) const {
+ const Value arr = vpOperand[0]->evaluateInternal(vars);
+ uassert(17041, str::stream() << getOpName() << "'s argument must be an array, but is "
+ << typeName(arr.getType()),
+ arr.getType() == Array);
+ const vector<Value>& array = arr.getArray();
+ for (vector<Value>::const_iterator it = array.begin(); it != array.end(); ++it) {
+ if (it->coerceToBool()) {
+ return Value(true);
+ }
+ }
+ return Value(false);
}
- intrusive_ptr<ExpressionNary> (*ExpressionAnd::getFactory() const)() {
- return ExpressionAnd::create;
+ REGISTER_EXPRESSION("$anyElementTrue", ExpressionAnyElementTrue::parse);
+ const char *ExpressionAnyElementTrue::getOpName() const {
+ return "$anyElementTrue";
}
/* -------------------- ExpressionCoerceToBool ------------------------- */
- ExpressionCoerceToBool::~ExpressionCoerceToBool() {
- }
-
intrusive_ptr<ExpressionCoerceToBool> ExpressionCoerceToBool::create(
const intrusive_ptr<Expression> &pExpression) {
intrusive_ptr<ExpressionCoerceToBool> pNew(
@@ -533,190 +587,95 @@ namespace mongo {
return intrusive_ptr<Expression>(this);
}
- void ExpressionCoerceToBool::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionCoerceToBool::addDependencies(DepsTracker* deps, vector<string>* path) const {
pExpression->addDependencies(deps);
}
- Value ExpressionCoerceToBool::evaluate(const Document& pDocument) const {
- Value pResult(pExpression->evaluate(pDocument));
+ Value ExpressionCoerceToBool::evaluateInternal(Variables* vars) const {
+ Value pResult(pExpression->evaluateInternal(vars));
bool b = pResult.coerceToBool();
if (b)
return Value(true);
return Value(false);
}
- void ExpressionCoerceToBool::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
- // Serializing as an $and expression which will become a CoerceToBool
- BSONObjBuilder sub (pBuilder->subobjStart(fieldName));
- BSONArrayBuilder arr (sub.subarrayStart("$and"));
- pExpression->addToBsonArray(&arr);
- arr.doneFast();
- sub.doneFast();
- }
-
- void ExpressionCoerceToBool::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
- // Serializing as an $and expression which will become a CoerceToBool
- BSONObjBuilder sub (pBuilder->subobjStart());
- BSONArrayBuilder arr (sub.subarrayStart("$and"));
- pExpression->addToBsonArray(&arr);
- arr.doneFast();
- sub.doneFast();
+ Value ExpressionCoerceToBool::serialize(bool explain) const {
+ // When not explaining, serialize to an $and expression. When parsed, the $and expression
+ // will be optimized back into a ExpressionCoerceToBool.
+ const char* name = explain ? "$coerceToBool" : "$and";
+ return Value(DOC(name << DOC_ARRAY(pExpression->serialize(explain))));
}
/* ----------------------- ExpressionCompare --------------------------- */
- ExpressionCompare::~ExpressionCompare() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createEq() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(EQ));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createNe() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(NE));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createGt() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(GT));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createGte() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(GTE));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createLt() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(LT));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createLte() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(LTE));
- return pExpression;
- }
-
- intrusive_ptr<ExpressionNary> ExpressionCompare::createCmp() {
- intrusive_ptr<ExpressionCompare> pExpression(
- new ExpressionCompare(CMP));
- return pExpression;
- }
-
- ExpressionCompare::ExpressionCompare(CmpOp theCmpOp):
- ExpressionNary(),
- cmpOp(theCmpOp) {
- }
-
- void ExpressionCompare::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
- }
+ REGISTER_EXPRESSION("$cmp",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::CMP));
+ REGISTER_EXPRESSION("$eq",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::EQ));
+ REGISTER_EXPRESSION("$gt",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::GT));
+ REGISTER_EXPRESSION("$gte",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::GTE));
+ REGISTER_EXPRESSION("$lt",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::LT));
+ REGISTER_EXPRESSION("$lte",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::LTE));
+ REGISTER_EXPRESSION("$ne",
+ boost::bind(ExpressionCompare::parse, _1, _2, ExpressionCompare::NE));
+ intrusive_ptr<Expression> ExpressionCompare::parse(
+ BSONElement bsonExpr,
+ const VariablesParseState& vps,
+ CmpOp op) {
+
+ intrusive_ptr<ExpressionCompare> expr = new ExpressionCompare(op);
+ ExpressionVector args = parseArguments(bsonExpr, vps);
+ expr->validateArguments(args);
+ expr->vpOperand = args;
+ return expr;
+ }
+
+ ExpressionCompare::ExpressionCompare(CmpOp theCmpOp)
+ : cmpOp(theCmpOp)
+ {}
- /*
- Lookup table for truth value returns
- */
+namespace {
+ // Lookup table for truth value returns
struct CmpLookup {
- bool truthValue[3]; /* truth value for -1, 0, 1 */
- Expression::CmpOp reverse; /* reverse comparison operator */
- char name[5]; /* string name (w/trailing '\0') */
+ const bool truthValue[3]; // truth value for -1, 0, 1
+ const ExpressionCompare::CmpOp reverse; // reverse(b,a) returns the same as op(a,b)
+ const char name[5]; // string name with trailing '\0'
};
static const CmpLookup cmpLookup[7] = {
- /* -1 0 1 reverse name */
- /* EQ */ { { false, true, false }, Expression::EQ, "$eq" },
- /* NE */ { { true, false, true }, Expression::NE, "$ne" },
- /* GT */ { { false, false, true }, Expression::LT, "$gt" },
- /* GTE */ { { false, true, true }, Expression::LTE, "$gte" },
- /* LT */ { { true, false, false }, Expression::GT, "$lt" },
- /* LTE */ { { true, true, false }, Expression::GTE, "$lte" },
- /* CMP */ { { false, false, false }, Expression::CMP, "$cmp" },
+ /* -1 0 1 reverse name */
+ /* EQ */ { { false, true, false }, ExpressionCompare::EQ, "$eq" },
+ /* NE */ { { true, false, true }, ExpressionCompare::NE, "$ne" },
+ /* GT */ { { false, false, true }, ExpressionCompare::LT, "$gt" },
+ /* GTE */ { { false, true, true }, ExpressionCompare::LTE, "$gte" },
+ /* LT */ { { true, false, false }, ExpressionCompare::GT, "$lt" },
+ /* LTE */ { { true, true, false }, ExpressionCompare::GTE, "$lte" },
+
+ // CMP is special. Only name is used.
+ /* CMP */ { { false, false, false }, ExpressionCompare::CMP, "$cmp" },
};
+}
- intrusive_ptr<Expression> ExpressionCompare::optimize() {
- /* first optimize the comparison operands */
- intrusive_ptr<Expression> pE(ExpressionNary::optimize());
-
- /*
- If the result of optimization is no longer a comparison, there's
- nothing more we can do.
- */
- ExpressionCompare *pCmp = dynamic_cast<ExpressionCompare *>(pE.get());
- if (!pCmp)
- return pE;
-
- /* check to see if optimizing comparison operator is supported */
- CmpOp newOp = pCmp->cmpOp;
- // CMP and NE cannot use ExpressionFieldRange which is what this optimization uses
- if (newOp == CMP || newOp == NE)
- return pE;
-
- /*
- There's one localized optimization we recognize: a comparison
- between a field and a constant. If we recognize that pattern,
- replace it with an ExpressionFieldRange.
-
- When looking for this pattern, note that the operands could appear
- in any order. If we need to reverse the sense of the comparison to
- put it into the required canonical form, do so.
- */
- intrusive_ptr<Expression> pLeft(pCmp->vpOperand[0]);
- intrusive_ptr<Expression> pRight(pCmp->vpOperand[1]);
- intrusive_ptr<ExpressionFieldPath> pFieldPath(
- dynamic_pointer_cast<ExpressionFieldPath>(pLeft));
- intrusive_ptr<ExpressionConstant> pConstant;
- if (pFieldPath.get()) {
- pConstant = dynamic_pointer_cast<ExpressionConstant>(pRight);
- if (!pConstant.get())
- return pE; // there's nothing more we can do
- }
- else {
- /* if the first operand wasn't a path, see if it's a constant */
- pConstant = dynamic_pointer_cast<ExpressionConstant>(pLeft);
- if (!pConstant.get())
- return pE; // there's nothing more we can do
+ Value ExpressionCompare::evaluateInternal(Variables* vars) const {
+ Value pLeft(vpOperand[0]->evaluateInternal(vars));
+ Value pRight(vpOperand[1]->evaluateInternal(vars));
- /* the left operand was a constant; see if the right is a path */
- pFieldPath = dynamic_pointer_cast<ExpressionFieldPath>(pRight);
- if (!pFieldPath.get())
- return pE; // there's nothing more we can do
+ int cmp = Value::compare(pLeft, pRight);
- /* these were not in canonical order, so reverse the sense */
- newOp = cmpLookup[newOp].reverse;
+ // Make cmp one of 1, 0, or -1.
+ if (cmp == 0) {
+ // leave as 0
+ } else if (cmp < 0) {
+ cmp = -1;
+ } else if (cmp > 0) {
+ cmp = 1;
}
- return ExpressionFieldRange::create(
- pFieldPath, newOp, pConstant->getValue());
- }
-
- Value ExpressionCompare::evaluate(const Document& pDocument) const {
- checkArgCount(2);
- Value pLeft(vpOperand[0]->evaluate(pDocument));
- Value pRight(vpOperand[1]->evaluate(pDocument));
-
- int cmp = signum(Value::compare(pLeft, pRight));
-
- if (cmpOp == CMP) {
- switch(cmp) {
- case -1:
- case 0:
- case 1:
- return Value(cmp);
-
- default:
- verify(false); // CW TODO internal error
- }
- }
+ if (cmpOp == CMP)
+ return Value(cmp);
bool returnValue = cmpLookup[cmpOp].truthValue[cmp + 1];
return Value(returnValue);
@@ -728,19 +687,12 @@ namespace mongo {
/* ------------------------- ExpressionConcat ----------------------------- */
- ExpressionConcat::~ExpressionConcat() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionConcat::create() {
- return new ExpressionConcat();
- }
-
- Value ExpressionConcat::evaluate(const Document& input) const {
+ Value ExpressionConcat::evaluateInternal(Variables* vars) const {
const size_t n = vpOperand.size();
StringBuilder result;
for (size_t i = 0; i < n; ++i) {
- Value val = vpOperand[i]->evaluate(input);
+ Value val = vpOperand[i]->evaluateInternal(vars);
if (val.nullish())
return Value(BSONNULL);
@@ -751,59 +703,71 @@ namespace mongo {
result << val.coerceToString();
}
- return Value::createString(result.str());
+ return Value(result.str());
}
+ REGISTER_EXPRESSION("$concat", ExpressionConcat::parse);
const char *ExpressionConcat::getOpName() const {
return "$concat";
}
/* ----------------------- ExpressionCond ------------------------------ */
- ExpressionCond::~ExpressionCond() {
+ Value ExpressionCond::evaluateInternal(Variables* vars) const {
+ Value pCond(vpOperand[0]->evaluateInternal(vars));
+ int idx = pCond.coerceToBool() ? 1 : 2;
+ return vpOperand[idx]->evaluateInternal(vars);
}
- intrusive_ptr<ExpressionNary> ExpressionCond::create() {
- intrusive_ptr<ExpressionCond> pExpression(new ExpressionCond());
- return pExpression;
- }
+ intrusive_ptr<Expression> ExpressionCond::parse(
+ BSONElement expr,
+ const VariablesParseState& vps) {
- ExpressionCond::ExpressionCond():
- ExpressionNary() {
- }
+ if (expr.type() != Object) {
+ return Base::parse(expr, vps);
+ }
+ verify(str::equals(expr.fieldName(), "$cond"));
- void ExpressionCond::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(3);
- ExpressionNary::addOperand(pExpression);
- }
+ intrusive_ptr<ExpressionCond> ret = new ExpressionCond();
+ ret->vpOperand.resize(3);
- Value ExpressionCond::evaluate(const Document& pDocument) const {
- checkArgCount(3);
- Value pCond(vpOperand[0]->evaluate(pDocument));
- int idx = pCond.coerceToBool() ? 1 : 2;
- return vpOperand[idx]->evaluate(pDocument);
+ const BSONObj args = expr.embeddedObject();
+ BSONForEach(arg, args) {
+ if (str::equals(arg.fieldName(), "if")) {
+ ret->vpOperand[0] = parseOperand(arg, vps);
+ } else if (str::equals(arg.fieldName(), "then")) {
+ ret->vpOperand[1] = parseOperand(arg, vps);
+ } else if (str::equals(arg.fieldName(), "else")) {
+ ret->vpOperand[2] = parseOperand(arg, vps);
+ } else {
+ uasserted(17083, str::stream()
+ << "Unrecognized parameter to $cond: " << arg.fieldName());
+ }
+ }
+
+ uassert(17080, "Missing 'if' parameter to $cond",
+ ret->vpOperand[0]);
+ uassert(17081, "Missing 'then' parameter to $cond",
+ ret->vpOperand[1]);
+ uassert(17082, "Missing 'else' parameter to $cond",
+ ret->vpOperand[2]);
+
+ return ret;
}
+ REGISTER_EXPRESSION("$cond", ExpressionCond::parse);
const char *ExpressionCond::getOpName() const {
return "$cond";
}
/* ---------------------- ExpressionConstant --------------------------- */
- ExpressionConstant::~ExpressionConstant() {
+ intrusive_ptr<Expression> ExpressionConstant::parse(
+ BSONElement exprElement,
+ const VariablesParseState& vps) {
+ return new ExpressionConstant(Value(exprElement));
}
- intrusive_ptr<ExpressionConstant> ExpressionConstant::createFromBsonElement(
- BSONElement *pBsonElement) {
- intrusive_ptr<ExpressionConstant> pEC(
- new ExpressionConstant(pBsonElement));
- return pEC;
- }
-
- ExpressionConstant::ExpressionConstant(BSONElement *pBsonElement):
- pValue(Value::createFromBsonElement(pBsonElement)) {
- }
intrusive_ptr<ExpressionConstant> ExpressionConstant::create(const Value& pValue) {
intrusive_ptr<ExpressionConstant> pEC(new ExpressionConstant(pValue));
@@ -818,176 +782,68 @@ namespace mongo {
return intrusive_ptr<Expression>(this);
}
- void ExpressionConstant::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionConstant::addDependencies(DepsTracker* deps, vector<string>* path) const {
/* nothing to do */
}
- Value ExpressionConstant::evaluate(const Document& pDocument) const {
+ Value ExpressionConstant::evaluateInternal(Variables* vars) const {
return pValue;
}
- void ExpressionConstant::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
- /*
- If we don't need an expression, but can use a naked scalar,
- do the regular thing.
-
- This is geared to handle $project, which uses expressions as a cue
- that the field is a new virtual field rather than just an
- inclusion (or exclusion):
- { $project : {
- x : true, // include
- y : { $const: true }
- }}
-
- This can happen as a result of optimizations. For example, the
- above may have originally been
- { $project : {
- x : true, // include
- y : { $eq:["foo", "foo"] }
- }}
- When this is optimized, the $eq will be replaced with true. However,
- if the pipeline is rematerialized (as happens for a split for
- sharding) and sent to another node, it will now have
- y : true
- which will look like an inclusion rather than a computed field.
- */
- if (!requireExpression) {
- pValue.addToBsonObj(pBuilder, fieldName);
- return;
- }
-
- // We require an expression, so build one here, and use that.
- BSONObjBuilder constBuilder (pBuilder->subobjStart(fieldName));
- pValue.addToBsonObj(&constBuilder, getOpName());
- constBuilder.done();
- }
-
- void ExpressionConstant::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
- pValue.addToBsonArray(pBuilder);
+ Value ExpressionConstant::serialize(bool explain) const {
+ return serializeConstant(pValue);
}
+ REGISTER_EXPRESSION("$const", ExpressionConstant::parse);
+ REGISTER_EXPRESSION("$literal", ExpressionConstant::parse); // alias
const char *ExpressionConstant::getOpName() const {
return "$const";
}
/* ---------------------- ExpressionDayOfMonth ------------------------- */
- ExpressionDayOfMonth::~ExpressionDayOfMonth() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionDayOfMonth::create() {
- intrusive_ptr<ExpressionDayOfMonth> pExpression(new ExpressionDayOfMonth());
- return pExpression;
- }
-
- ExpressionDayOfMonth::ExpressionDayOfMonth():
- ExpressionNary() {
- }
-
- void ExpressionDayOfMonth::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
-
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionDayOfMonth::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionDayOfMonth::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_mday);
+ return Value(date.tm_mday);
}
+ REGISTER_EXPRESSION("$dayOfMonth", ExpressionDayOfMonth::parse);
const char *ExpressionDayOfMonth::getOpName() const {
return "$dayOfMonth";
}
/* ------------------------- ExpressionDayOfWeek ----------------------------- */
- ExpressionDayOfWeek::~ExpressionDayOfWeek() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionDayOfWeek::create() {
- intrusive_ptr<ExpressionDayOfWeek> pExpression(new ExpressionDayOfWeek());
- return pExpression;
- }
-
- ExpressionDayOfWeek::ExpressionDayOfWeek():
- ExpressionNary() {
- }
-
- void ExpressionDayOfWeek::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionDayOfWeek::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionDayOfWeek::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_wday+1); // MySQL uses 1-7 tm uses 0-6
+ return Value(date.tm_wday+1); // MySQL uses 1-7 tm uses 0-6
}
+ REGISTER_EXPRESSION("$dayOfWeek", ExpressionDayOfWeek::parse);
const char *ExpressionDayOfWeek::getOpName() const {
return "$dayOfWeek";
}
/* ------------------------- ExpressionDayOfYear ----------------------------- */
- ExpressionDayOfYear::~ExpressionDayOfYear() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionDayOfYear::create() {
- intrusive_ptr<ExpressionDayOfYear> pExpression(new ExpressionDayOfYear());
- return pExpression;
- }
-
- ExpressionDayOfYear::ExpressionDayOfYear():
- ExpressionNary() {
- }
-
- void ExpressionDayOfYear::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionDayOfYear::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionDayOfYear::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_yday+1); // MySQL uses 1-366 tm uses 0-365
+ return Value(date.tm_yday+1); // MySQL uses 1-366 tm uses 0-365
}
+ REGISTER_EXPRESSION("$dayOfYear", ExpressionDayOfYear::parse);
const char *ExpressionDayOfYear::getOpName() const {
return "$dayOfYear";
}
/* ----------------------- ExpressionDivide ---------------------------- */
- ExpressionDivide::~ExpressionDivide() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionDivide::create() {
- intrusive_ptr<ExpressionDivide> pExpression(new ExpressionDivide());
- return pExpression;
- }
-
- ExpressionDivide::ExpressionDivide():
- ExpressionNary() {
- }
-
- void ExpressionDivide::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionDivide::evaluate(const Document& pDocument) const {
- checkArgCount(2);
- Value lhs = vpOperand[0]->evaluate(pDocument);
- Value rhs = vpOperand[1]->evaluate(pDocument);
+ Value ExpressionDivide::evaluateInternal(Variables* vars) const {
+ Value lhs = vpOperand[0]->evaluateInternal(vars);
+ Value rhs = vpOperand[1]->evaluateInternal(vars);
if (lhs.numeric() && rhs.numeric()) {
double numer = lhs.coerceToDouble();
@@ -995,7 +851,7 @@ namespace mongo {
uassert(16608, "can't $divide by zero",
denom != 0);
- return Value::createDouble(numer / denom);
+ return Value(numer / denom);
}
else if (lhs.nullish() || rhs.nullish()) {
return Value(BSONNULL);
@@ -1008,25 +864,28 @@ namespace mongo {
}
}
+ REGISTER_EXPRESSION("$divide", ExpressionDivide::parse);
const char *ExpressionDivide::getOpName() const {
return "$divide";
}
/* ---------------------- ExpressionObject --------------------------- */
- ExpressionObject::~ExpressionObject() {
- }
-
intrusive_ptr<ExpressionObject> ExpressionObject::create() {
- intrusive_ptr<ExpressionObject> pExpression(new ExpressionObject());
- return pExpression;
+ return new ExpressionObject(false);
}
- ExpressionObject::ExpressionObject(): _excludeId(false) {
+ intrusive_ptr<ExpressionObject> ExpressionObject::createRoot() {
+ return new ExpressionObject(true);
}
+ ExpressionObject::ExpressionObject(bool atRoot)
+ : _excludeId(false)
+ , _atRoot(atRoot)
+ {}
+
intrusive_ptr<Expression> ExpressionObject::optimize() {
- for (ExpressionMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ for (FieldMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
if (it->second)
it->second = it->second->optimize();
}
@@ -1035,20 +894,20 @@ namespace mongo {
}
bool ExpressionObject::isSimple() {
- for (ExpressionMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ for (FieldMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
if (it->second && !it->second->isSimple())
return false;
}
return true;
}
- void ExpressionObject::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionObject::addDependencies(DepsTracker* deps, vector<string>* path) const {
string pathStr;
if (path) {
if (path->empty()) {
// we are in the top level of a projection so _id is implicit
if (!_excludeId)
- deps.insert("_id");
+ deps->fields.insert("_id");
}
else {
FieldPath f (*path);
@@ -1061,7 +920,7 @@ namespace mongo {
}
- for (ExpressionMap::const_iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ for (FieldMap::const_iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
if (it->second) {
if (path) path->push_back(it->first);
it->second->addDependencies(deps, path);
@@ -1071,35 +930,33 @@ namespace mongo {
uassert(16407, "inclusion not supported in objects nested in $expressions",
path);
- deps.insert(pathStr + it->first);
+ deps->fields.insert(pathStr + it->first);
}
}
}
void ExpressionObject::addToDocument(
MutableDocument& out,
- const Document& pDocument,
- const Document& rootDoc
+ const Document& currentDoc,
+ Variables* vars
) const
{
- const bool atRoot = (pDocument == rootDoc);
-
- ExpressionMap::const_iterator end = _expressions.end();
+ FieldMap::const_iterator end = _expressions.end();
// This is used to mark fields we've done so that we can add the ones we haven't
set<string> doneFields;
- FieldIterator fields(pDocument);
+ FieldIterator fields(currentDoc);
while(fields.more()) {
Document::FieldPair field (fields.next());
// TODO don't make a new string here
const string fieldName = field.first.toString();
- ExpressionMap::const_iterator exprIter = _expressions.find(fieldName);
+ FieldMap::const_iterator exprIter = _expressions.find(fieldName);
// This field is not supposed to be in the output (unless it is _id)
if (exprIter == end) {
- if (!_excludeId && atRoot && field.first == "_id") {
+ if (!_excludeId && _atRoot && field.first == "_id") {
// _id from the root doc is always included (until exclusion is supported)
// not updating doneFields since "_id" isn't in _expressions
out.addField(field.first, field.second);
@@ -1123,10 +980,10 @@ namespace mongo {
if ((valueType != Object && valueType != Array) || !exprObj ) {
// This expression replace the whole field
- Value pValue(expr->evaluate(rootDoc));
+ Value pValue(expr->evaluateInternal(vars));
// don't add field if nothing was found in the subobject
- if (exprObj && pValue.getDocument()->getFieldCount() == 0)
+ if (exprObj && pValue.getDocument().empty())
continue;
/*
@@ -1147,8 +1004,8 @@ namespace mongo {
*/
if (valueType == Object) {
MutableDocument sub (exprObj->getSizeHint());
- exprObj->addToDocument(sub, field.second.getDocument(), rootDoc);
- out.addField(field.first, Value(sub.freeze()));
+ exprObj->addToDocument(sub, field.second.getDocument(), vars);
+ out.addField(field.first, sub.freezeToValue());
}
else if (valueType == Array) {
/*
@@ -1164,11 +1021,11 @@ namespace mongo {
continue;
MutableDocument doc (exprObj->getSizeHint());
- exprObj->addToDocument(doc, input[i].getDocument(), rootDoc);
- result.push_back(Value(doc.freeze()));
+ exprObj->addToDocument(doc, input[i].getDocument(), vars);
+ result.push_back(doc.freezeToValue());
}
- out.addField(field.first, Value(result));
+ out.addField(field.first, Value::consume(result));
}
else {
verify( false );
@@ -1180,7 +1037,7 @@ namespace mongo {
/* add any remaining fields we haven't already taken care of */
for (vector<string>::const_iterator i(_order.begin()); i!=_order.end(); ++i) {
- ExpressionMap::const_iterator it = _expressions.find(*i);
+ FieldMap::const_iterator it = _expressions.find(*i);
string fieldName(it->first);
/* if we've already dealt with this field, above, do nothing */
@@ -1191,7 +1048,7 @@ namespace mongo {
if (!it->second)
continue;
- Value pValue(it->second->evaluate(rootDoc));
+ Value pValue(it->second->evaluateInternal(vars));
/*
Don't add non-existent values (note: different from NULL or Undefined);
@@ -1203,7 +1060,7 @@ namespace mongo {
// don't add field if nothing was found in the subobject
if (dynamic_cast<ExpressionObject*>(it->second.get())
- && pValue.getDocument()->getFieldCount() == 0)
+ && pValue.getDocument().empty())
continue;
@@ -1216,18 +1073,18 @@ namespace mongo {
return _expressions.size() + (_excludeId ? 0 : 1);
}
- Document ExpressionObject::evaluateDocument(const Document& pDocument) const {
+ Document ExpressionObject::evaluateDocument(Variables* vars) const {
/* create and populate the result */
MutableDocument out (getSizeHint());
addToDocument(out,
Document(), // No inclusion field matching.
- pDocument);
+ vars);
return out.freeze();
}
- Value ExpressionObject::evaluate(const Document& pDocument) const {
- return Value::createDocument(evaluateDocument(pDocument));
+ Value ExpressionObject::evaluateInternal(Variables* vars) const {
+ return Value(evaluateDocument(vars));
}
void ExpressionObject::addField(const FieldPath &fieldPath,
@@ -1288,66 +1145,77 @@ namespace mongo {
addField(theFieldPath, NULL);
}
- void ExpressionObject::documentToBson(BSONObjBuilder *pBuilder, bool requireExpression) const {
+ Value ExpressionObject::serialize(bool explain) const {
+ MutableDocument valBuilder;
if (_excludeId)
- pBuilder->appendBool("_id", false);
+ valBuilder["_id"] = Value(false);
for (vector<string>::const_iterator it(_order.begin()); it!=_order.end(); ++it) {
string fieldName = *it;
verify(_expressions.find(fieldName) != _expressions.end());
intrusive_ptr<Expression> expr = _expressions.find(fieldName)->second;
-
+
if (!expr) {
// this is inclusion, not an expression
- pBuilder->appendBool(fieldName, true);
+ valBuilder[fieldName] = Value(true);
}
else {
- expr->addToBsonObj(pBuilder, fieldName, requireExpression);
+ valBuilder[fieldName] = expr->serialize(explain);
}
}
+ return valBuilder.freezeToValue();
}
- void ExpressionObject::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
+ /* --------------------- ExpressionFieldPath --------------------------- */
- BSONObjBuilder objBuilder (pBuilder->subobjStart(fieldName));
- documentToBson(&objBuilder, requireExpression);
- objBuilder.done();
+ // this is the old deprecated version only used by tests not using variables
+ intrusive_ptr<ExpressionFieldPath> ExpressionFieldPath::create(const string& fieldPath) {
+ return new ExpressionFieldPath("CURRENT." + fieldPath, Variables::ROOT_ID);
}
- void ExpressionObject::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
+ // this is the new version that supports every syntax
+ intrusive_ptr<ExpressionFieldPath> ExpressionFieldPath::parse(
+ const string& raw,
+ const VariablesParseState& vps) {
- BSONObjBuilder objBuilder (pBuilder->subobjStart());
- documentToBson(&objBuilder, false);
- objBuilder.done();
- }
+ uassert(16873, str::stream() << "FieldPath '" << raw << "' doesn't start with $",
+ raw.c_str()[0] == '$'); // c_str()[0] is always a valid reference.
- /* --------------------- ExpressionFieldPath --------------------------- */
+ uassert(16872, str::stream() << "'$' by itself is not a valid FieldPath",
+ raw.size() >= 2); // need at least "$" and either "$" or a field name
- ExpressionFieldPath::~ExpressionFieldPath() {
+ if (raw[1] == '$') {
+ const StringData rawSD = raw;
+ const StringData fieldPath = rawSD.substr(2); // strip off $$
+ const StringData varName = fieldPath.substr(0, fieldPath.find('.'));
+ Variables::uassertValidNameForUserRead(varName);
+ return new ExpressionFieldPath(fieldPath.toString(), vps.getVariable(varName));
+ }
+ else {
+ return new ExpressionFieldPath("CURRENT." + raw.substr(1), // strip the "$" prefix
+ vps.getVariable("CURRENT"));
+ }
}
- intrusive_ptr<ExpressionFieldPath> ExpressionFieldPath::create(
- const string &fieldPath) {
- intrusive_ptr<ExpressionFieldPath> pExpression(
- new ExpressionFieldPath(fieldPath));
- return pExpression;
- }
- ExpressionFieldPath::ExpressionFieldPath(
- const string &theFieldPath):
- fieldPath(theFieldPath) {
- }
+ ExpressionFieldPath::ExpressionFieldPath(const string& theFieldPath, Variables::Id variable)
+ : _fieldPath(theFieldPath)
+ , _variable(variable)
+ {}
intrusive_ptr<Expression> ExpressionFieldPath::optimize() {
/* nothing can be done for these */
return intrusive_ptr<Expression>(this);
}
- void ExpressionFieldPath::addDependencies(set<string>& deps, vector<string>* path) const {
- deps.insert(fieldPath.getPath(false));
+ void ExpressionFieldPath::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ if (_variable == Variables::ROOT_ID) { // includes CURRENT when it is equivalent to ROOT.
+ if (_fieldPath.getPathLength() == 1) {
+ deps->needWholeDocument = true; // need full doc if just "$$ROOT"
+ } else {
+ deps->fields.insert(_fieldPath.tail().getPath(false));
+ }
+ }
}
Value ExpressionFieldPath::evaluatePathArray(size_t index, const Value& input) const {
@@ -1365,18 +1233,18 @@ namespace mongo {
result.push_back(nested);
}
- return Value::createArray(result);
+ return Value::consume(result);
}
Value ExpressionFieldPath::evaluatePath(size_t index, const Document& input) const {
// Note this function is very hot so it is important that is is well optimized.
// In particular, all return paths should support RVO.
/* if we've hit the end of the path, stop */
- if (index == fieldPath.getPathLength() - 1)
- return input[fieldPath.getFieldName(index)];
+ if (index == _fieldPath.getPathLength() - 1)
+ return input[_fieldPath.getFieldName(index)];
// Try to dive deeper
- const Value val = input[fieldPath.getFieldName(index)];
+ const Value val = input[_fieldPath.getFieldName(index)];
switch (val.getType()) {
case Object:
return evaluatePath(index+1, val.getDocument());
@@ -1389,420 +1257,312 @@ namespace mongo {
}
}
- Value ExpressionFieldPath::evaluate(const Document& pDocument) const {
- return evaluatePath(0, pDocument);
- }
+ Value ExpressionFieldPath::evaluateInternal(Variables* vars) const {
+ if (_fieldPath.getPathLength() == 1) // get the whole variable
+ return vars->getValue(_variable);
- void ExpressionFieldPath::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
- pBuilder->append(fieldName, fieldPath.getPath(true));
+ if (_variable == Variables::ROOT_ID) {
+ // ROOT is always a document so use optimized code path
+ return evaluatePath(1, vars->getRoot());
+ }
+
+ Value var = vars->getValue(_variable);
+ switch (var.getType()) {
+ case Object: return evaluatePath(1, var.getDocument());
+ case Array: return evaluatePathArray(1, var);
+ default: return Value();
+ }
}
- void ExpressionFieldPath::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
- pBuilder->append(getFieldPath(true));
+ Value ExpressionFieldPath::serialize(bool explain) const {
+ if (_fieldPath.getFieldName(0) == "CURRENT" && _fieldPath.getPathLength() > 1) {
+ // use short form for "$$CURRENT.foo" but not just "$$CURRENT"
+ return Value("$" + _fieldPath.tail().getPath(false));
+ }
+ else {
+ return Value("$$" + _fieldPath.getPath(false));
+ }
}
- /* --------------------- ExpressionFieldRange -------------------------- */
+ /* ------------------------- ExpressionLet ----------------------------- */
- ExpressionFieldRange::~ExpressionFieldRange() {
- }
+ REGISTER_EXPRESSION("$let", ExpressionLet::parse);
+ intrusive_ptr<Expression> ExpressionLet::parse(
+ BSONElement expr,
+ const VariablesParseState& vpsIn) {
- intrusive_ptr<Expression> ExpressionFieldRange::optimize() {
- /* if there is no range to match, this will never evaluate true */
- if (!pRange.get())
- return ExpressionConstant::create(Value(false));
+ verify(str::equals(expr.fieldName(), "$let"));
- /*
- If we ended up with a double un-ended range, anything matches. I
- don't know how that can happen, given intersect()'s interface, but
- here it is, just in case.
- */
- if (pRange->pBottom.missing() && pRange->pTop.missing())
- return ExpressionConstant::create(Value(true));
+ uassert(16874, "$let only supports an object as it's argument",
+ expr.type() == Object);
+ const BSONObj args = expr.embeddedObject();
- /*
- In all other cases, we have to test candidate values. The
- intersect() method has already optimized those tests, so there
- aren't any more optimizations to look for here.
- */
- return intrusive_ptr<Expression>(this);
- }
+ // varsElem must be parsed before inElem regardless of BSON order.
+ BSONElement varsElem;
+ BSONElement inElem;
+ BSONForEach(arg, args) {
+ if (str::equals(arg.fieldName(), "vars")) {
+ varsElem = arg;
+ } else if (str::equals(arg.fieldName(), "in")) {
+ inElem = arg;
+ } else {
+ uasserted(16875, str::stream()
+ << "Unrecognized parameter to $let: " << arg.fieldName());
+ }
+ }
- void ExpressionFieldRange::addDependencies(set<string>& deps, vector<string>* path) const {
- pFieldPath->addDependencies(deps);
- }
+ uassert(16876, "Missing 'vars' parameter to $let",
+ !varsElem.eoo());
+ uassert(16877, "Missing 'in' parameter to $let",
+ !inElem.eoo());
- Value ExpressionFieldRange::evaluate(const Document& pDocument) const {
- /* if there's no range, there can't be a match */
- if (!pRange.get())
- return Value(false);
+ // parse "vars"
+ VariablesParseState vpsSub(vpsIn); // vpsSub gets our vars, vpsIn doesn't.
+ VariableMap vars;
+ BSONForEach(varElem, varsElem.embeddedObjectUserCheck()) {
+ const string varName = varElem.fieldName();
+ Variables::uassertValidNameForUserWrite(varName);
+ Variables::Id id = vpsSub.defineVariable(varName);
- /* get the value of the specified field */
- Value pValue(pFieldPath->evaluate(pDocument));
+ vars[id] = NameAndExpression(varName,
+ parseOperand(varElem, vpsIn)); // only has outer vars
+ }
- /* see if it fits within any of the ranges */
- if (pRange->contains(pValue))
- return Value(true);
+ // parse "in"
+ intrusive_ptr<Expression> subExpression = parseOperand(inElem, vpsSub); // has our vars
- return Value(false);
+ return new ExpressionLet(vars, subExpression);
}
- void ExpressionFieldRange::addToBson(Builder *pBuilder) const {
- if (!pRange.get()) {
- /* nothing will satisfy this predicate */
- pBuilder->append(false);
- return;
- }
+ ExpressionLet::ExpressionLet(const VariableMap& vars, intrusive_ptr<Expression> subExpression)
+ : _variables(vars)
+ , _subExpression(subExpression)
+ {}
- if (pRange->pTop.missing() && pRange->pBottom.missing()) {
- /* any value will satisfy this predicate */
- pBuilder->append(true);
- return;
+ intrusive_ptr<Expression> ExpressionLet::optimize() {
+ if (_variables.empty()) {
+ // we aren't binding any variables so just return the subexpression
+ return _subExpression->optimize();
}
- // FIXME Append constant values using the $const operator. SERVER-6769
-
- // FIXME This checks pointer equality not value equality.
- if (pRange->pTop == pRange->pBottom) {
- BSONArrayBuilder operands;
- pFieldPath->addToBsonArray(&operands);
- pRange->pTop.addToBsonArray(&operands);
-
- BSONObjBuilder equals;
- equals.append("$eq", operands.arr());
- pBuilder->append(&equals);
- return;
+ for (VariableMap::iterator it=_variables.begin(), end=_variables.end(); it != end; ++it) {
+ it->second.expression = it->second.expression->optimize();
}
- BSONObjBuilder leftOperator;
- if (!pRange->pBottom.missing()) {
- BSONArrayBuilder leftOperands;
- pFieldPath->addToBsonArray(&leftOperands);
- pRange->pBottom.addToBsonArray(&leftOperands);
- leftOperator.append(
- (pRange->bottomOpen ? "$gt" : "$gte"),
- leftOperands.arr());
+ // TODO be smarter with constant "variables"
+ _subExpression = _subExpression->optimize();
- if (pRange->pTop.missing()) {
- pBuilder->append(&leftOperator);
- return;
- }
+ return this;
+ }
+
+ Value ExpressionLet::serialize(bool explain) const {
+ MutableDocument vars;
+ for (VariableMap::const_iterator it=_variables.begin(), end=_variables.end();
+ it != end; ++it) {
+ vars[it->second.name] = it->second.expression->serialize(explain);
}
- BSONObjBuilder rightOperator;
- if (!pRange->pTop.missing()) {
- BSONArrayBuilder rightOperands;
- pFieldPath->addToBsonArray(&rightOperands);
- pRange->pTop.addToBsonArray(&rightOperands);
- rightOperator.append(
- (pRange->topOpen ? "$lt" : "$lte"),
- rightOperands.arr());
+ return Value(DOC("$let" << DOC("vars" << vars.freeze()
+ << "in" << _subExpression->serialize(explain))
+ ));
+ }
- if (pRange->pBottom.missing()) {
- pBuilder->append(&rightOperator);
- return;
- }
+ Value ExpressionLet::evaluateInternal(Variables* vars) const {
+ for (VariableMap::const_iterator it=_variables.begin(), end=_variables.end();
+ it != end; ++it) {
+ // It is guaranteed at parse-time that these expressions don't use the variable ids we
+ // are setting
+ vars->setValue(it->first,
+ it->second.expression->evaluateInternal(vars));
}
- BSONArrayBuilder andOperands;
- andOperands.append(leftOperator.done());
- andOperands.append(rightOperator.done());
- BSONObjBuilder andOperator;
- andOperator.append("$and", andOperands.arr());
- pBuilder->append(&andOperator);
+ return _subExpression->evaluateInternal(vars);
}
- void ExpressionFieldRange::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
- BuilderObj builder(pBuilder, fieldName);
- addToBson(&builder);
- }
+ void ExpressionLet::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ for (VariableMap::const_iterator it=_variables.begin(), end=_variables.end();
+ it != end; ++it) {
+ it->second.expression->addDependencies(deps);
+ }
- void ExpressionFieldRange::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
- BuilderArray builder(pBuilder);
- addToBson(&builder);
+ // TODO be smarter when CURRENT is a bound variable
+ _subExpression->addDependencies(deps);
}
- void ExpressionFieldRange::toMatcherBson(
- BSONObjBuilder *pBuilder) const {
- verify(pRange.get()); // otherwise, we can't do anything
- /* if there are no endpoints, then every value is accepted */
- if (pRange->pBottom.missing() && pRange->pTop.missing())
- return; // nothing to add to the predicate
+ /* ------------------------- ExpressionMap ----------------------------- */
- /* we're going to need the field path */
- string fieldPath(pFieldPath->getFieldPath(false));
+ REGISTER_EXPRESSION("$map", ExpressionMap::parse);
+ intrusive_ptr<Expression> ExpressionMap::parse(
+ BSONElement expr,
+ const VariablesParseState& vpsIn) {
- BSONObjBuilder range;
- if (!pRange->pBottom.missing()) {
- /* the test for equality doesn't generate a subobject */
- if (pRange->pBottom == pRange->pTop) {
- pRange->pBottom.addToBsonObj(pBuilder, fieldPath);
- return;
- }
+ verify(str::equals(expr.fieldName(), "$map"));
- pRange->pBottom.addToBsonObj(
- pBuilder, (pRange->bottomOpen ? "$gt" : "$gte"));
- }
+ uassert(16878, "$map only supports an object as it's argument",
+ expr.type() == Object);
- if (!pRange->pTop.missing()) {
- pRange->pTop.addToBsonObj(
- pBuilder, (pRange->topOpen ? "$lt" : "$lte"));
+ // "in" must be parsed after "as" regardless of BSON order
+ BSONElement inputElem;
+ BSONElement asElem;
+ BSONElement inElem;
+ const BSONObj args = expr.embeddedObject();
+ BSONForEach(arg, args) {
+ if (str::equals(arg.fieldName(), "input")) {
+ inputElem = arg;
+ } else if (str::equals(arg.fieldName(), "as")) {
+ asElem = arg;
+ } else if (str::equals(arg.fieldName(), "in")) {
+ inElem = arg;
+ } else {
+ uasserted(16879, str::stream()
+ << "Unrecognized parameter to $map: " << arg.fieldName());
+ }
}
- pBuilder->append(fieldPath, range.done());
- }
-
- intrusive_ptr<ExpressionFieldRange> ExpressionFieldRange::create(
- const intrusive_ptr<ExpressionFieldPath> &pFieldPath, CmpOp cmpOp,
- const Value& pValue) {
- intrusive_ptr<ExpressionFieldRange> pE(
- new ExpressionFieldRange(pFieldPath, cmpOp, pValue));
- return pE;
- }
+ uassert(16880, "Missing 'input' parameter to $map",
+ !inputElem.eoo());
+ uassert(16881, "Missing 'as' parameter to $map",
+ !asElem.eoo());
+ uassert(16882, "Missing 'in' parameter to $map",
+ !inElem.eoo());
- ExpressionFieldRange::ExpressionFieldRange(
- const intrusive_ptr<ExpressionFieldPath> &pTheFieldPath, CmpOp cmpOp,
- const Value& pValue):
- pFieldPath(pTheFieldPath),
- pRange(new Range(cmpOp, pValue)) {
- }
+ // parse "input"
+ intrusive_ptr<Expression> input = parseOperand(inputElem, vpsIn); // only has outer vars
- void ExpressionFieldRange::intersect(CmpOp cmpOp, const Value& pValue) {
+ // parse "as"
+ VariablesParseState vpsSub(vpsIn); // vpsSub gets our vars, vpsIn doesn't.
+ string varName = asElem.str();
+ Variables::uassertValidNameForUserWrite(varName);
+ Variables::Id varId = vpsSub.defineVariable(varName);
- /* create the new range */
- scoped_ptr<Range> pNew(new Range(cmpOp, pValue));
+ // parse "in"
+ intrusive_ptr<Expression> in = parseOperand(inElem, vpsSub); // has access to map variable
- /*
- Go through the range list. For every range, either add the
- intersection of that to the range list, or if there is none, the
- original range. This has the effect of restricting overlapping
- ranges, but leaving non-overlapping ones as-is.
- */
- pRange.reset(pRange->intersect(pNew.get()));
+ return new ExpressionMap(varName, varId, input, in);
}
- ExpressionFieldRange::Range::Range(CmpOp cmpOp, const Value& pValue):
- bottomOpen(false),
- topOpen(false),
- pBottom(),
- pTop() {
- switch(cmpOp) {
-
- case EQ:
- pBottom = pTop = pValue;
- break;
-
- case GT:
- bottomOpen = true;
- /* FALLTHROUGH */
- case GTE:
- topOpen = true;
- pBottom = pValue;
- break;
-
- case LT:
- topOpen = true;
- /* FALLTHROUGH */
- case LTE:
- bottomOpen = true;
- pTop = pValue;
- break;
-
- case NE:
- case CMP:
- verify(false); // not allowed
- break;
- }
- }
+ ExpressionMap::ExpressionMap(const string& varName,
+ Variables::Id varId,
+ intrusive_ptr<Expression> input,
+ intrusive_ptr<Expression> each)
+ : _varName(varName)
+ , _varId(varId)
+ , _input(input)
+ , _each(each)
+ {}
- ExpressionFieldRange::Range::Range(const Range &rRange):
- bottomOpen(rRange.bottomOpen),
- topOpen(rRange.topOpen),
- pBottom(rRange.pBottom),
- pTop(rRange.pTop) {
+ intrusive_ptr<Expression> ExpressionMap::optimize() {
+ // TODO handle when _input is constant
+ _input = _input->optimize();
+ _each = _each->optimize();
+ return this;
}
- ExpressionFieldRange::Range::Range(
- const Value& pTheBottom, bool theBottomOpen,
- const Value& pTheTop, bool theTopOpen):
- bottomOpen(theBottomOpen),
- topOpen(theTopOpen),
- pBottom(pTheBottom),
- pTop(pTheTop) {
+ Value ExpressionMap::serialize(bool explain) const {
+ return Value(DOC("$map" << DOC("input" << _input->serialize(explain)
+ << "as" << _varName
+ << "in" << _each->serialize(explain)
+ )));
}
- ExpressionFieldRange::Range *ExpressionFieldRange::Range::intersect(
- const Range *pRange) const {
- /*
- Find the max of the bottom end of the ranges.
+ Value ExpressionMap::evaluateInternal(Variables* vars) const {
+ // guaranteed at parse time that this isn't using our _varId
+ const Value inputVal = _input->evaluateInternal(vars);
+ if (inputVal.nullish())
+ return Value(BSONNULL);
- Start by assuming the maximum is from pRange. Then, if we have
- values of our own, see if they're greater.
- */
- Value pMaxBottom(pRange->pBottom);
- bool maxBottomOpen = pRange->bottomOpen;
- if (!pBottom.missing()) {
- if (pRange->pBottom.missing()) {
- pMaxBottom = pBottom;
- maxBottomOpen = bottomOpen;
- }
- else {
- const int cmp = Value::compare(pBottom, pRange->pBottom);
- if (cmp == 0)
- maxBottomOpen = bottomOpen || pRange->bottomOpen;
- else if (cmp > 0) {
- pMaxBottom = pBottom;
- maxBottomOpen = bottomOpen;
- }
- }
- }
+ uassert(16883, str::stream() << "input to $map must be an Array not "
+ << typeName(inputVal.getType()),
+ inputVal.getType() == Array);
- /*
- Find the minimum of the tops of the ranges.
+ const vector<Value>& input = inputVal.getArray();
- Start by assuming the minimum is from pRange. Then, if we have
- values of our own, see if they are less.
- */
- Value pMinTop(pRange->pTop);
- bool minTopOpen = pRange->topOpen;
- if (!pTop.missing()) {
- if (pRange->pTop.missing()) {
- pMinTop = pTop;
- minTopOpen = topOpen;
- }
- else {
- const int cmp = Value::compare(pTop, pRange->pTop);
- if (cmp == 0)
- minTopOpen = topOpen || pRange->topOpen;
- else if (cmp < 0) {
- pMinTop = pTop;
- minTopOpen = topOpen;
- }
- }
- }
+ if (input.empty())
+ return inputVal;
- /*
- If the intersections didn't create a disjoint set, create the
- new range.
- */
- if (Value::compare(pMaxBottom, pMinTop) <= 0)
- return new Range(pMaxBottom, maxBottomOpen, pMinTop, minTopOpen);
+ vector<Value> output;
+ output.reserve(input.size());
+ for (size_t i=0; i < input.size(); i++) {
+ vars->setValue(_varId, input[i]);
- /* if we got here, the intersection is empty */
- return NULL;
- }
+ Value toInsert = _each->evaluateInternal(vars);
+ if (toInsert.missing())
+ toInsert = Value(BSONNULL); // can't insert missing values into array
- bool ExpressionFieldRange::Range::contains(const Value& pValue) const {
- if (!pBottom.missing()) {
- const int cmp = Value::compare(pValue, pBottom);
- if (cmp < 0)
- return false;
- if (bottomOpen && (cmp == 0))
- return false;
+ output.push_back(toInsert);
}
- if (!pTop.missing()) {
- const int cmp = Value::compare(pValue, pTop);
- if (cmp > 0)
- return false;
- if (topOpen && (cmp == 0))
- return false;
- }
+ return Value::consume(output);
+ }
- return true;
+ void ExpressionMap::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ _input->addDependencies(deps);
+ _each->addDependencies(deps);
}
- /* ------------------------- ExpressionMillisecond ----------------------------- */
+ /* ------------------------- ExpressionMeta ----------------------------- */
+
+ REGISTER_EXPRESSION("$meta", ExpressionMeta::parse);
+ intrusive_ptr<Expression> ExpressionMeta::parse(
+ BSONElement expr,
+ const VariablesParseState& vpsIn) {
- ExpressionMillisecond::~ExpressionMillisecond() {
+ uassert(17307, "$meta only supports String arguments",
+ expr.type() == String);
+ uassert(17308, "Unsupported argument to $meta: " + expr.String(),
+ expr.String() == "textScore");
+
+ return new ExpressionMeta();
}
- intrusive_ptr<ExpressionNary> ExpressionMillisecond::create() {
- intrusive_ptr<ExpressionMillisecond> pExpression(new ExpressionMillisecond());
- return pExpression;
+ Value ExpressionMeta::serialize(bool explain) const {
+ return Value(DOC("$meta" << "textScore"));
}
- ExpressionMillisecond::ExpressionMillisecond():
- ExpressionNary() {
+ Value ExpressionMeta::evaluateInternal(Variables* vars) const {
+ const Document& root = vars->getRoot();
+ return root.hasTextScore()
+ ? Value(root.getTextScore())
+ : Value();
}
- void ExpressionMillisecond::addOperand(const intrusive_ptr<Expression>& pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
+ void ExpressionMeta::addDependencies(DepsTracker* deps, vector<string>* path) const {
+ deps->needTextScore = true;
}
- Value ExpressionMillisecond::evaluate(const Document& document) const {
- checkArgCount(1);
- Value date(vpOperand[0]->evaluate(document));
+ /* ------------------------- ExpressionMillisecond ----------------------------- */
+
+ Value ExpressionMillisecond::evaluateInternal(Variables* vars) const {
+ Value date(vpOperand[0]->evaluateInternal(vars));
const int ms = date.coerceToDate() % 1000LL;
- return Value::createInt( ms >= 0 ? ms : 1000 + ms );
+ // adding 1000 since dates before 1970 would have negative ms
+ return Value(ms >= 0 ? ms : 1000 + ms);
}
+ REGISTER_EXPRESSION("$millisecond", ExpressionMillisecond::parse);
const char *ExpressionMillisecond::getOpName() const {
return "$millisecond";
}
/* ------------------------- ExpressionMinute -------------------------- */
- ExpressionMinute::~ExpressionMinute() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionMinute::create() {
- intrusive_ptr<ExpressionMinute> pExpression(new ExpressionMinute());
- return pExpression;
- }
-
- ExpressionMinute::ExpressionMinute():
- ExpressionNary() {
- }
-
- void ExpressionMinute::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionMinute::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionMinute::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_min);
+ return Value(date.tm_min);
}
+ REGISTER_EXPRESSION("$minute", ExpressionMinute::parse);
const char *ExpressionMinute::getOpName() const {
return "$minute";
}
/* ----------------------- ExpressionMod ---------------------------- */
- ExpressionMod::~ExpressionMod() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionMod::create() {
- intrusive_ptr<ExpressionMod> pExpression(new ExpressionMod());
- return pExpression;
- }
-
- ExpressionMod::ExpressionMod():
- ExpressionNary() {
- }
-
- void ExpressionMod::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionMod::evaluate(const Document& pDocument) const {
- checkArgCount(2);
- Value lhs = vpOperand[0]->evaluate(pDocument);
- Value rhs = vpOperand[1]->evaluate(pDocument);
+ Value ExpressionMod::evaluateInternal(Variables* vars) const {
+ Value lhs = vpOperand[0]->evaluateInternal(vars);
+ Value rhs = vpOperand[1]->evaluateInternal(vars);
BSONType leftType = lhs.getType();
BSONType rightType = rhs.getType();
@@ -1821,19 +1581,19 @@ namespace mongo {
// Integer-valued double case is handled below
double left = lhs.coerceToDouble();
- return Value::createDouble(fmod(left, right));
+ return Value(fmod(left, right));
}
else if (leftType == NumberLong || rightType == NumberLong) {
// if either is long, return long
long long left = lhs.coerceToLong();
long long rightLong = rhs.coerceToLong();
- return Value::createLong(left % rightLong);
+ return Value(left % rightLong);
}
// lastly they must both be ints, return int
int left = lhs.coerceToInt();
int rightInt = rhs.coerceToInt();
- return Value::createInt(left % rightInt);
+ return Value(left % rightInt);
}
else if (lhs.nullish() || rhs.nullish()) {
return Value(BSONNULL);
@@ -1846,55 +1606,27 @@ namespace mongo {
}
}
+ REGISTER_EXPRESSION("$mod", ExpressionMod::parse);
const char *ExpressionMod::getOpName() const {
return "$mod";
}
/* ------------------------ ExpressionMonth ----------------------------- */
- ExpressionMonth::~ExpressionMonth() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionMonth::create() {
- intrusive_ptr<ExpressionMonth> pExpression(new ExpressionMonth());
- return pExpression;
- }
-
- ExpressionMonth::ExpressionMonth():
- ExpressionNary() {
- }
-
- void ExpressionMonth::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionMonth::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionMonth::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_mon + 1); // MySQL uses 1-12 tm uses 0-11
+ return Value(date.tm_mon + 1); // MySQL uses 1-12 tm uses 0-11
}
+ REGISTER_EXPRESSION("$month", ExpressionMonth::parse);
const char *ExpressionMonth::getOpName() const {
return "$month";
}
/* ------------------------- ExpressionMultiply ----------------------------- */
- ExpressionMultiply::~ExpressionMultiply() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionMultiply::create() {
- intrusive_ptr<ExpressionMultiply> pExpression(new ExpressionMultiply());
- return pExpression;
- }
-
- ExpressionMultiply::ExpressionMultiply():
- ExpressionNary() {
- }
-
- Value ExpressionMultiply::evaluate(const Document& pDocument) const {
+ Value ExpressionMultiply::evaluateInternal(Variables* vars) const {
/*
We'll try to return the narrowest possible result value. To do that
without creating intermediate Values, do the arithmetic for double
@@ -1907,7 +1639,7 @@ namespace mongo {
const size_t n = vpOperand.size();
for(size_t i = 0; i < n; ++i) {
- Value val = vpOperand[i]->evaluate(pDocument);
+ Value val = vpOperand[i]->evaluateInternal(vars);
if (val.numeric()) {
productType = Value::getWidestNumeric(productType, val.getType());
@@ -1925,328 +1657,168 @@ namespace mongo {
}
if (productType == NumberDouble)
- return Value::createDouble(doubleProduct);
+ return Value(doubleProduct);
else if (productType == NumberLong)
- return Value::createLong(longProduct);
+ return Value(longProduct);
else if (productType == NumberInt)
return Value::createIntOrLong(longProduct);
else
massert(16418, "$multiply resulted in a non-numeric type", false);
}
+ REGISTER_EXPRESSION("$multiply", ExpressionMultiply::parse);
const char *ExpressionMultiply::getOpName() const {
- return "$multiply";
- }
-
- intrusive_ptr<ExpressionNary> (*ExpressionMultiply::getFactory() const)() {
- return ExpressionMultiply::create;
+ return "$multiply";
}
/* ------------------------- ExpressionHour ----------------------------- */
- ExpressionHour::~ExpressionHour() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionHour::create() {
- intrusive_ptr<ExpressionHour> pExpression(new ExpressionHour());
- return pExpression;
- }
-
- ExpressionHour::ExpressionHour():
- ExpressionNary() {
- }
-
- void ExpressionHour::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionHour::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionHour::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_hour);
+ return Value(date.tm_hour);
}
+ REGISTER_EXPRESSION("$hour", ExpressionHour::parse);
const char *ExpressionHour::getOpName() const {
return "$hour";
}
/* ----------------------- ExpressionIfNull ---------------------------- */
- ExpressionIfNull::~ExpressionIfNull() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionIfNull::create() {
- intrusive_ptr<ExpressionIfNull> pExpression(new ExpressionIfNull());
- return pExpression;
- }
-
- ExpressionIfNull::ExpressionIfNull():
- ExpressionNary() {
- }
-
- void ExpressionIfNull::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionIfNull::evaluate(const Document& pDocument) const {
- checkArgCount(2);
-
- Value pLeft(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionIfNull::evaluateInternal(Variables* vars) const {
+ Value pLeft(vpOperand[0]->evaluateInternal(vars));
if (!pLeft.nullish())
return pLeft;
- Value pRight(vpOperand[1]->evaluate(pDocument));
+ Value pRight(vpOperand[1]->evaluateInternal(vars));
return pRight;
}
+ REGISTER_EXPRESSION("$ifNull", ExpressionIfNull::parse);
const char *ExpressionIfNull::getOpName() const {
return "$ifNull";
}
/* ------------------------ ExpressionNary ----------------------------- */
- ExpressionNary::ExpressionNary():
- vpOperand() {
- }
-
intrusive_ptr<Expression> ExpressionNary::optimize() {
- unsigned constCount = 0; // count of constant operands
- unsigned stringCount = 0; // count of constant string operands
const size_t n = vpOperand.size();
+
+ // optimize sub-expressions and count constants
+ unsigned constCount = 0;
for(size_t i = 0; i < n; ++i) {
- intrusive_ptr<Expression> pNew(vpOperand[i]->optimize());
-
- /* subsitute the optimized expression */
- vpOperand[i] = pNew;
-
- /* check to see if the result was a constant */
- const ExpressionConstant *pConst =
- dynamic_cast<ExpressionConstant *>(pNew.get());
- if (pConst) {
- ++constCount;
- if (pConst->getValue().getType() == String)
- ++stringCount;
+ intrusive_ptr<Expression> optimized = vpOperand[i]->optimize();
+
+ // substitute the optimized expression
+ vpOperand[i] = optimized;
+
+ // check to see if the result was a constant
+ if (dynamic_cast<ExpressionConstant*>(optimized.get())) {
+ constCount++;
}
}
- /*
- If all the operands are constant, we can replace this expression
- with a constant. We can find the value by evaluating this
- expression over a NULL Document because evaluating the
- ExpressionConstant never refers to the argument Document.
- */
+ // If all the operands are constant, we can replace this expression with a constant. Using
+ // an empty Variables since it will never be accessed.
if (constCount == n) {
- Value pResult(evaluate(Document()));
+ Variables emptyVars;
+ Value pResult(evaluateInternal(&emptyVars));
intrusive_ptr<Expression> pReplacement(
ExpressionConstant::create(pResult));
return pReplacement;
}
- /*
- If there are any strings, we can't re-arrange anything, so stop
- now.
+ // Remaining optimizations are only for associative and commutative expressions.
+ if (!isAssociativeAndCommutative())
+ return this;
- LATER: we could concatenate adjacent strings as a special case.
- */
- if (stringCount)
- return intrusive_ptr<Expression>(this);
-
- /*
- If there's no more than one constant, then we can't do any
- constant folding, so don't bother going any further.
- */
- if (constCount <= 1)
- return intrusive_ptr<Expression>(this);
-
- /*
- If the operator isn't commutative or associative, there's nothing
- more we can do. We test that by seeing if we can get a factory;
- if we can, we can use it to construct a temporary expression which
- we'll evaluate to collapse as many constants as we can down to
- a single one.
- */
- intrusive_ptr<ExpressionNary> (*const pFactory)() = getFactory();
- if (!pFactory)
- return intrusive_ptr<Expression>(this);
-
- /*
- Create a new Expression that will be the replacement for this one.
- We actually create two: one to hold constant expressions, and
- one to hold non-constants. Once we've got these, we evaluate
- the constant expression to produce a single value, as above.
- We then add this operand to the end of the non-constant expression,
- and return that.
- */
- intrusive_ptr<ExpressionNary> pNew((*pFactory)());
- intrusive_ptr<ExpressionNary> pConst((*pFactory)());
- for(size_t i = 0; i < n; ++i) {
- intrusive_ptr<Expression> pE(vpOperand[i]);
- if (dynamic_cast<ExpressionConstant *>(pE.get()))
- pConst->addOperand(pE);
+ // Process vpOperand to split it into constant and nonconstant vectors.
+ // This can leave vpOperand in an invalid state that is cleaned up after the loop.
+ ExpressionVector constExprs;
+ ExpressionVector nonConstExprs;
+ for(size_t i = 0; i < vpOperand.size(); ++i) { // NOTE: vpOperand grows in loop
+ intrusive_ptr<Expression> expr = vpOperand[i];
+ if (dynamic_cast<ExpressionConstant*>(expr.get())) {
+ constExprs.push_back(expr);
+ }
else {
- /*
- If the child operand is the same type as this, then we can
- extract its operands and inline them here because we already
- know this is commutative and associative because it has a
- factory. We can detect sameness of the child operator by
- checking for equality of the factory
-
- Note we don't have to do this recursively, because we
- called optimize() on all the children first thing in
- this call to optimize().
- */
- ExpressionNary *pNary =
- dynamic_cast<ExpressionNary *>(pE.get());
- if (!pNary)
- pNew->addOperand(pE);
+ // If the child operand is the same type as this, then we can
+ // extract its operands and inline them here because we know
+ // this is commutative and associative. We detect sameness of
+ // the child operator by checking for equality of the opNames
+ ExpressionNary* nary = dynamic_cast<ExpressionNary*>(expr.get());
+ if (!nary || !str::equals(nary->getOpName(), getOpName())) {
+ nonConstExprs.push_back(expr);
+ }
else {
- intrusive_ptr<ExpressionNary> (*const pChildFactory)() =
- pNary->getFactory();
- if (pChildFactory != pFactory)
- pNew->addOperand(pE);
- else {
- /* same factory, so flatten */
- size_t nChild = pNary->vpOperand.size();
- for(size_t iChild = 0; iChild < nChild; ++iChild) {
- intrusive_ptr<Expression> pCE(
- pNary->vpOperand[iChild]);
- if (dynamic_cast<ExpressionConstant *>(pCE.get()))
- pConst->addOperand(pCE);
- else
- pNew->addOperand(pCE);
- }
- }
+ // same expression, so flatten by adding to vpOperand which
+ // will be processed later in this loop.
+ vpOperand.insert(vpOperand.end(),
+ nary->vpOperand.begin(),
+ nary->vpOperand.end());
}
}
}
- /*
- If there was only one constant, add it to the end of the expression
- operand vector.
- */
- if (pConst->vpOperand.size() == 1)
- pNew->addOperand(pConst->vpOperand[0]);
- else if (pConst->vpOperand.size() > 1) {
- /*
- If there was more than one constant, collapse all the constants
- together before adding the result to the end of the expression
- operand vector.
- */
- Value pResult(pConst->evaluate(Document()));
- pNew->addOperand(ExpressionConstant::create(pResult));
+ // collapse all constant expressions (if any)
+ Value constValue;
+ if (!constExprs.empty()) {
+ vpOperand = constExprs;
+ Variables emptyVars;
+ constValue = evaluateInternal(&emptyVars);
}
- return pNew;
+ // now set the final expression list with constant (if any) at the end
+ vpOperand = nonConstExprs;
+ if (!constExprs.empty()) {
+ vpOperand.push_back(ExpressionConstant::create(constValue));
+ }
+
+ return this;
}
- void ExpressionNary::addDependencies(set<string>& deps, vector<string>* path) const {
+ void ExpressionNary::addDependencies(DepsTracker* deps, vector<string>* path) const {
for(ExpressionVector::const_iterator i(vpOperand.begin());
i != vpOperand.end(); ++i) {
(*i)->addDependencies(deps);
}
}
- void ExpressionNary::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
+ void ExpressionNary::addOperand(const intrusive_ptr<Expression>& pExpression) {
vpOperand.push_back(pExpression);
}
- intrusive_ptr<ExpressionNary> (*ExpressionNary::getFactory() const)() {
- return NULL;
- }
-
- void ExpressionNary::toBson(BSONObjBuilder *pBuilder, const char *pOpName) const {
+ Value ExpressionNary::serialize(bool explain) const {
const size_t nOperand = vpOperand.size();
-
+ vector<Value> array;
/* build up the array */
- BSONArrayBuilder arrBuilder (pBuilder->subarrayStart(pOpName));
- for(size_t i = 0; i < nOperand; ++i)
- vpOperand[i]->addToBsonArray(&arrBuilder);
- arrBuilder.doneFast();
- }
-
- void ExpressionNary::addToBsonObj(BSONObjBuilder *pBuilder,
- StringData fieldName,
- bool requireExpression) const {
- BSONObjBuilder exprBuilder;
- toBson(&exprBuilder, getOpName());
- pBuilder->append(fieldName, exprBuilder.done());
- }
-
- void ExpressionNary::addToBsonArray(
- BSONArrayBuilder *pBuilder) const {
- BSONObjBuilder exprBuilder;
- toBson(&exprBuilder, getOpName());
- pBuilder->append(exprBuilder.done());
- }
-
- void ExpressionNary::checkArgLimit(unsigned maxArgs) const {
- uassert(15993, str::stream() << getOpName() <<
- " only takes " << maxArgs <<
- " operand" << (maxArgs == 1 ? "" : "s"),
- vpOperand.size() < maxArgs);
- }
+ for(size_t i = 0; i < nOperand; i++)
+ array.push_back(vpOperand[i]->serialize(explain));
- void ExpressionNary::checkArgCount(unsigned reqArgs) const {
- uassert(15997, str::stream() << getOpName() <<
- ": insufficient operands; " << reqArgs <<
- " required, only got " << vpOperand.size(),
- vpOperand.size() == reqArgs);
+ return Value(DOC(getOpName() << array));
}
/* ------------------------- ExpressionNot ----------------------------- */
- ExpressionNot::~ExpressionNot() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionNot::create() {
- intrusive_ptr<ExpressionNot> pExpression(new ExpressionNot());
- return pExpression;
- }
-
- ExpressionNot::ExpressionNot():
- ExpressionNary() {
- }
-
- void ExpressionNot::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionNot::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pOp(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionNot::evaluateInternal(Variables* vars) const {
+ Value pOp(vpOperand[0]->evaluateInternal(vars));
bool b = pOp.coerceToBool();
return Value(!b);
}
+ REGISTER_EXPRESSION("$not", ExpressionNot::parse);
const char *ExpressionNot::getOpName() const {
return "$not";
}
/* -------------------------- ExpressionOr ----------------------------- */
- ExpressionOr::~ExpressionOr() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionOr::create() {
- intrusive_ptr<ExpressionNary> pExpression(new ExpressionOr());
- return pExpression;
- }
-
- ExpressionOr::ExpressionOr():
- ExpressionNary() {
- }
-
- Value ExpressionOr::evaluate(const Document& pDocument) const {
+ Value ExpressionOr::evaluateInternal(Variables* vars) const {
const size_t n = vpOperand.size();
for(size_t i = 0; i < n; ++i) {
- Value pValue(vpOperand[i]->evaluate(pDocument));
+ Value pValue(vpOperand[i]->evaluateInternal(vars));
if (pValue.coerceToBool())
return Value(true);
}
@@ -2254,20 +1826,6 @@ namespace mongo {
return Value(false);
}
- void ExpressionOr::toMatcherBson(
- BSONObjBuilder *pBuilder) const {
- BSONObjBuilder opArray;
- const size_t n = vpOperand.size();
- for(size_t i = 0; i < n; ++i)
- vpOperand[i]->toMatcherBson(&opArray);
-
- pBuilder->append("$or", opArray.done());
- }
-
- intrusive_ptr<ExpressionNary> (*ExpressionOr::getFactory() const)() {
- return ExpressionOr::create;
- }
-
intrusive_ptr<Expression> ExpressionOr::optimize() {
/* optimize the disjunction as much as possible */
intrusive_ptr<Expression> pE(ExpressionNary::optimize());
@@ -2295,7 +1853,7 @@ namespace mongo {
Evaluate and coerce the last argument to a boolean. If it's true,
then we can replace this entire expression.
*/
- bool last = pLast->evaluate(Document()).coerceToBool();
+ bool last = pConst->getValue().coerceToBool();
if (last) {
intrusive_ptr<ExpressionConstant> pFinal(
ExpressionConstant::create(Value(true)));
@@ -2321,64 +1879,286 @@ namespace mongo {
return pE;
}
+ REGISTER_EXPRESSION("$or", ExpressionOr::parse);
const char *ExpressionOr::getOpName() const {
return "$or";
}
/* ------------------------- ExpressionSecond ----------------------------- */
- ExpressionSecond::~ExpressionSecond() {
+ Value ExpressionSecond::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
+ tm date = pDate.coerceToTm();
+ return Value(date.tm_sec);
}
- intrusive_ptr<ExpressionNary> ExpressionSecond::create() {
- intrusive_ptr<ExpressionSecond> pExpression(new ExpressionSecond());
- return pExpression;
+ REGISTER_EXPRESSION("$second", ExpressionSecond::parse);
+ const char *ExpressionSecond::getOpName() const {
+ return "$second";
}
- ExpressionSecond::ExpressionSecond():
- ExpressionNary() {
+
+
+ namespace {
+ ValueSet arrayToSet(const Value& val) {
+ const vector<Value>& array = val.getArray();
+ return ValueSet(array.begin(), array.end());
+ }
}
- void ExpressionSecond::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
+ /* ----------------------- ExpressionSetDifference ---------------------------- */
+
+ Value ExpressionSetDifference::evaluateInternal(Variables* vars) const {
+ const Value lhs = vpOperand[0]->evaluateInternal(vars);
+ const Value rhs = vpOperand[1]->evaluateInternal(vars);
+
+ if (lhs.nullish() || rhs.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ uassert(17048, str::stream() << "both operands of $setDifference must be arrays. First "
+ << "argument is of type: " << typeName(lhs.getType()),
+ lhs.getType() == Array);
+ uassert(17049, str::stream() << "both operands of $setDifference must be arrays. Second "
+ << "argument is of type: " << typeName(rhs.getType()),
+ rhs.getType() == Array);
+
+ ValueSet rhsSet = arrayToSet(rhs);
+ const vector<Value>& lhsArray = lhs.getArray();
+ vector<Value> returnVec;
+
+ for (vector<Value>::const_iterator it = lhsArray.begin(); it != lhsArray.end(); ++it) {
+ // rhsSet serves the dual role of filtering out elements that were originally present
+ // in RHS and of eleminating duplicates from LHS
+ if (rhsSet.insert(*it).second) {
+ returnVec.push_back(*it);
+ }
+ }
+ return Value::consume(returnVec);
}
- Value ExpressionSecond::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
- tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_sec);
+ REGISTER_EXPRESSION("$setDifference", ExpressionSetDifference::parse);
+ const char *ExpressionSetDifference::getOpName() const {
+ return "$setDifference";
}
- const char *ExpressionSecond::getOpName() const {
- return "$second";
+ /* ----------------------- ExpressionSetEquals ---------------------------- */
+
+ void ExpressionSetEquals::validateArguments(const ExpressionVector& args) const {
+ uassert(17045, str::stream() << "$setEquals needs at least two arguments had: "
+ << args.size(),
+ args.size() >= 2);
}
- /* ----------------------- ExpressionStrcasecmp ---------------------------- */
+ Value ExpressionSetEquals::evaluateInternal(Variables* vars) const {
+ const size_t n = vpOperand.size();
+ std::set<Value> lhs;
- ExpressionStrcasecmp::~ExpressionStrcasecmp() {
+ for (size_t i = 0; i < n; i++) {
+ const Value nextEntry = vpOperand[i]->evaluateInternal(vars);
+ uassert(17044, str::stream() << "All operands of $setEquals must be arrays. One "
+ << "argument is of type: "
+ << typeName(nextEntry.getType()),
+ nextEntry.getType() == Array);
+
+ if (i == 0) {
+ lhs.insert(nextEntry.getArray().begin(), nextEntry.getArray().end());
+ }
+ else {
+ const std::set<Value> rhs(nextEntry.getArray().begin(), nextEntry.getArray().end());
+ if (lhs != rhs) {
+ return Value(false);
+ }
+ }
+ }
+ return Value(true);
}
- intrusive_ptr<ExpressionNary> ExpressionStrcasecmp::create() {
- intrusive_ptr<ExpressionStrcasecmp> pExpression(new ExpressionStrcasecmp());
- return pExpression;
+ REGISTER_EXPRESSION("$setEquals", ExpressionSetEquals::parse);
+ const char *ExpressionSetEquals::getOpName() const {
+ return "$setEquals";
}
- ExpressionStrcasecmp::ExpressionStrcasecmp():
- ExpressionNary() {
+ /* ----------------------- ExpressionSetIntersection ---------------------------- */
+
+ Value ExpressionSetIntersection::evaluateInternal(Variables* vars) const {
+ const size_t n = vpOperand.size();
+ ValueSet currentIntersection;
+ for (size_t i = 0; i < n; i++) {
+ const Value nextEntry = vpOperand[i]->evaluateInternal(vars);
+ if (nextEntry.nullish()) {
+ return Value(BSONNULL);
+ }
+ uassert(17047, str::stream() << "All operands of $setIntersection must be arrays. One "
+ << "argument is of type: "
+ << typeName(nextEntry.getType()),
+ nextEntry.getType() == Array);
+
+ if (i == 0) {
+ currentIntersection.insert(nextEntry.getArray().begin(),
+ nextEntry.getArray().end());
+ }
+ else {
+ ValueSet nextSet = arrayToSet(nextEntry);
+ if (currentIntersection.size() > nextSet.size()) {
+ // to iterate over whichever is the smaller set
+ nextSet.swap(currentIntersection);
+ }
+ ValueSet::iterator it = currentIntersection.begin();
+ while (it != currentIntersection.end()) {
+ if (!nextSet.count(*it)) {
+ ValueSet::iterator del = it;
+ ++it;
+ currentIntersection.erase(del);
+ }
+ else {
+ ++it;
+ }
+ }
+ }
+ if (currentIntersection.empty()) {
+ break;
+ }
+ }
+ vector<Value> result = vector<Value>(currentIntersection.begin(),
+ currentIntersection.end());
+ return Value::consume(result);
}
- void ExpressionStrcasecmp::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
+ REGISTER_EXPRESSION("$setIntersection", ExpressionSetIntersection::parse);
+ const char *ExpressionSetIntersection::getOpName() const {
+ return "$setIntersection";
+ }
+
+ /* ----------------------- ExpressionSetIsSubset ---------------------------- */
+
+namespace {
+ Value setIsSubsetHelper(const vector<Value>& lhs, const ValueSet& rhs) {
+ // do not shortcircuit when lhs.size() > rhs.size()
+ // because lhs can have redundant entries
+ for (vector<Value>::const_iterator it = lhs.begin(); it != lhs.end(); ++it) {
+ if (!rhs.count(*it)) {
+ return Value(false);
+ }
+ }
+ return Value(true);
+ }
+}
+
+ Value ExpressionSetIsSubset::evaluateInternal(Variables* vars) const {
+ const Value lhs = vpOperand[0]->evaluateInternal(vars);
+ const Value rhs = vpOperand[1]->evaluateInternal(vars);
+
+ uassert(17046, str::stream() << "both operands of $setIsSubset must be arrays. First "
+ << "argument is of type: " << typeName(lhs.getType()),
+ lhs.getType() == Array);
+ uassert(17042, str::stream() << "both operands of $setIsSubset must be arrays. Second "
+ << "argument is of type: " << typeName(rhs.getType()),
+ rhs.getType() == Array);
+
+ return setIsSubsetHelper(lhs.getArray(), arrayToSet(rhs));
+ }
+
+ /**
+ * This class handles the case where the RHS set is constant.
+ *
+ * Since it is constant we can construct the hashset once which makes the runtime performance
+ * effectively constant with respect to the size of RHS. Large, constant RHS is expected to be a
+ * major use case for $redact and this has been verified to improve performance significantly.
+ */
+ class ExpressionSetIsSubset::Optimized : public ExpressionSetIsSubset {
+ public:
+ Optimized(const ValueSet& cachedRhsSet, const ExpressionVector& operands)
+ : _cachedRhsSet(cachedRhsSet)
+ {
+ vpOperand = operands;
+ }
+
+ virtual Value evaluateInternal(Variables* vars) const {
+ const Value lhs = vpOperand[0]->evaluateInternal(vars);
+
+ uassert(17310, str::stream() << "both operands of $setIsSubset must be arrays. First "
+ << "argument is of type: " << typeName(lhs.getType()),
+ lhs.getType() == Array);
+
+ return setIsSubsetHelper(lhs.getArray(), _cachedRhsSet);
+ }
+
+ private:
+ const ValueSet _cachedRhsSet;
+ };
+
+ intrusive_ptr<Expression> ExpressionSetIsSubset::optimize() {
+ // perfore basic optimizations
+ intrusive_ptr<Expression> optimized = ExpressionNary::optimize();
+
+ // if ExpressionNary::optimize() created a new value, return it directly
+ if (optimized.get() != this)
+ return optimized;
+
+ if (ExpressionConstant* ec = dynamic_cast<ExpressionConstant*>(vpOperand[1].get())) {
+ const Value rhs = ec->getValue();
+ uassert(17311, str::stream() << "both operands of $setIsSubset must be arrays. Second "
+ << "argument is of type: " << typeName(rhs.getType()),
+ rhs.getType() == Array);
+
+ return new Optimized(arrayToSet(rhs), vpOperand);
+ }
+
+ return optimized;
+ }
+
+ REGISTER_EXPRESSION("$setIsSubset", ExpressionSetIsSubset::parse);
+ const char *ExpressionSetIsSubset::getOpName() const {
+ return "$setIsSubset";
}
- Value ExpressionStrcasecmp::evaluate(const Document& pDocument) const {
- checkArgCount(2);
- Value pString1(vpOperand[0]->evaluate(pDocument));
- Value pString2(vpOperand[1]->evaluate(pDocument));
+ /* ----------------------- ExpressionSetUnion ---------------------------- */
+
+ Value ExpressionSetUnion::evaluateInternal(Variables* vars) const {
+ ValueSet unionedSet;
+ const size_t n = vpOperand.size();
+ for (size_t i = 0; i < n; i++) {
+ const Value newEntries = vpOperand[i]->evaluateInternal(vars);
+ if (newEntries.nullish()) {
+ return Value(BSONNULL);
+ }
+ uassert(17043, str::stream() << "All operands of $setUnion must be arrays. One argument"
+ << " is of type: " << typeName(newEntries.getType()),
+ newEntries.getType() == Array);
+
+ unionedSet.insert(newEntries.getArray().begin(), newEntries.getArray().end());
+ }
+ vector<Value> result = vector<Value>(unionedSet.begin(), unionedSet.end());
+ return Value::consume(result);
+ }
+
+ REGISTER_EXPRESSION("$setUnion", ExpressionSetUnion::parse);
+ const char *ExpressionSetUnion::getOpName() const {
+ return "$setUnion";
+ }
+
+ /* ----------------------- ExpressionSize ---------------------------- */
+
+ Value ExpressionSize::evaluateInternal(Variables* vars) const {
+ Value array = vpOperand[0]->evaluateInternal(vars);
+
+ uassert(17124, str::stream() << "The argument to $size must be an Array, but was of type: "
+ << typeName(array.getType()),
+ array.getType() == Array);
+ return Value::createIntOrLong(array.getArray().size());
+ }
+
+ REGISTER_EXPRESSION("$size", ExpressionSize::parse);
+ const char *ExpressionSize::getOpName() const {
+ return "$size";
+ }
+
+ /* ----------------------- ExpressionStrcasecmp ---------------------------- */
+
+ Value ExpressionStrcasecmp::evaluateInternal(Variables* vars) const {
+ Value pString1(vpOperand[0]->evaluateInternal(vars));
+ Value pString2(vpOperand[1]->evaluateInternal(vars));
/* boost::iequals returns a bool not an int so strings must actually be allocated */
string str1 = boost::to_upper_copy( pString1.coerceToString() );
@@ -2393,35 +2173,17 @@ namespace mongo {
return Value(-1);
}
+ REGISTER_EXPRESSION("$strcasecmp", ExpressionStrcasecmp::parse);
const char *ExpressionStrcasecmp::getOpName() const {
return "$strcasecmp";
}
/* ----------------------- ExpressionSubstr ---------------------------- */
- ExpressionSubstr::~ExpressionSubstr() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionSubstr::create() {
- intrusive_ptr<ExpressionSubstr> pExpression(new ExpressionSubstr());
- return pExpression;
- }
-
- ExpressionSubstr::ExpressionSubstr():
- ExpressionNary() {
- }
-
- void ExpressionSubstr::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(3);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionSubstr::evaluate(const Document& pDocument) const {
- checkArgCount(3);
- Value pString(vpOperand[0]->evaluate(pDocument));
- Value pLower(vpOperand[1]->evaluate(pDocument));
- Value pLength(vpOperand[2]->evaluate(pDocument));
+ Value ExpressionSubstr::evaluateInternal(Variables* vars) const {
+ Value pString(vpOperand[0]->evaluateInternal(vars));
+ Value pLower(vpOperand[1]->evaluateInternal(vars));
+ Value pLength(vpOperand[2]->evaluateInternal(vars));
string str = pString.coerceToString();
uassert(16034, str::stream() << getOpName() <<
@@ -2441,51 +2203,33 @@ namespace mongo {
if ( lower >= str.length() ) {
// If lower > str.length() then string::substr() will throw out_of_range, so return an
// empty string if lower is not a valid string index.
- return Value::createString( "" );
+ return Value("");
}
- return Value::createString( str.substr(lower, length) );
+ return Value(str.substr(lower, length));
}
+ REGISTER_EXPRESSION("$substr", ExpressionSubstr::parse);
const char *ExpressionSubstr::getOpName() const {
return "$substr";
}
/* ----------------------- ExpressionSubtract ---------------------------- */
- ExpressionSubtract::~ExpressionSubtract() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionSubtract::create() {
- intrusive_ptr<ExpressionSubtract> pExpression(new ExpressionSubtract());
- return pExpression;
- }
-
- ExpressionSubtract::ExpressionSubtract():
- ExpressionNary() {
- }
-
- void ExpressionSubtract::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(2);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionSubtract::evaluate(const Document& pDocument) const {
- checkArgCount(2);
- Value lhs = vpOperand[0]->evaluate(pDocument);
- Value rhs = vpOperand[1]->evaluate(pDocument);
+ Value ExpressionSubtract::evaluateInternal(Variables* vars) const {
+ Value lhs = vpOperand[0]->evaluateInternal(vars);
+ Value rhs = vpOperand[1]->evaluateInternal(vars);
BSONType diffType = Value::getWidestNumeric(rhs.getType(), lhs.getType());
if (diffType == NumberDouble) {
double right = rhs.coerceToDouble();
double left = lhs.coerceToDouble();
- return Value::createDouble(left - right);
+ return Value(left - right);
}
else if (diffType == NumberLong) {
long long right = rhs.coerceToLong();
long long left = lhs.coerceToLong();
- return Value::createLong(left - right);
+ return Value(left - right);
}
else if (diffType == NumberInt) {
long long right = rhs.coerceToLong();
@@ -2518,95 +2262,43 @@ namespace mongo {
}
}
+ REGISTER_EXPRESSION("$subtract", ExpressionSubtract::parse);
const char *ExpressionSubtract::getOpName() const {
return "$subtract";
}
/* ------------------------- ExpressionToLower ----------------------------- */
- ExpressionToLower::~ExpressionToLower() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionToLower::create() {
- intrusive_ptr<ExpressionToLower> pExpression(new ExpressionToLower());
- return pExpression;
- }
-
- ExpressionToLower::ExpressionToLower():
- ExpressionNary() {
- }
-
- void ExpressionToLower::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionToLower::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pString(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionToLower::evaluateInternal(Variables* vars) const {
+ Value pString(vpOperand[0]->evaluateInternal(vars));
string str = pString.coerceToString();
boost::to_lower(str);
- return Value::createString(str);
+ return Value(str);
}
+ REGISTER_EXPRESSION("$toLower", ExpressionToLower::parse);
const char *ExpressionToLower::getOpName() const {
return "$toLower";
}
/* ------------------------- ExpressionToUpper -------------------------- */
- ExpressionToUpper::~ExpressionToUpper() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionToUpper::create() {
- intrusive_ptr<ExpressionToUpper> pExpression(new ExpressionToUpper());
- return pExpression;
- }
-
- ExpressionToUpper::ExpressionToUpper():
- ExpressionNary() {
- }
-
- void ExpressionToUpper::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionToUpper::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pString(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionToUpper::evaluateInternal(Variables* vars) const {
+ Value pString(vpOperand[0]->evaluateInternal(vars));
string str(pString.coerceToString());
boost::to_upper(str);
- return Value::createString(str);
+ return Value(str);
}
+ REGISTER_EXPRESSION("$toUpper", ExpressionToUpper::parse);
const char *ExpressionToUpper::getOpName() const {
return "$toUpper";
}
/* ------------------------- ExpressionWeek ----------------------------- */
- ExpressionWeek::~ExpressionWeek() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionWeek::create() {
- intrusive_ptr<ExpressionWeek> pExpression(new ExpressionWeek());
- return pExpression;
- }
-
- ExpressionWeek::ExpressionWeek():
- ExpressionNary() {
- }
-
- void ExpressionWeek::addOperand(const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionWeek::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionWeek::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
int dayOfWeek = date.tm_wday;
int dayOfYear = date.tm_yday;
@@ -2624,40 +2316,23 @@ namespace mongo {
verify(int(str::toUnsigned(buf))==nextSundayWeek);
}
- return Value::createInt(nextSundayWeek);
+ return Value(nextSundayWeek);
}
+ REGISTER_EXPRESSION("$week", ExpressionWeek::parse);
const char *ExpressionWeek::getOpName() const {
return "$week";
}
/* ------------------------- ExpressionYear ----------------------------- */
- ExpressionYear::~ExpressionYear() {
- }
-
- intrusive_ptr<ExpressionNary> ExpressionYear::create() {
- intrusive_ptr<ExpressionYear> pExpression(new ExpressionYear());
- return pExpression;
- }
-
- ExpressionYear::ExpressionYear():
- ExpressionNary() {
- }
-
- void ExpressionYear::addOperand(
- const intrusive_ptr<Expression> &pExpression) {
- checkArgLimit(1);
- ExpressionNary::addOperand(pExpression);
- }
-
- Value ExpressionYear::evaluate(const Document& pDocument) const {
- checkArgCount(1);
- Value pDate(vpOperand[0]->evaluate(pDocument));
+ Value ExpressionYear::evaluateInternal(Variables* vars) const {
+ Value pDate(vpOperand[0]->evaluateInternal(vars));
tm date = pDate.coerceToTm();
- return Value::createInt(date.tm_year + 1900); // tm_year is years since 1900
+ return Value(date.tm_year + 1900); // tm_year is years since 1900
}
+ REGISTER_EXPRESSION("$year", ExpressionYear::parse);
const char *ExpressionYear::getOpName() const {
return "$year";
}