diff options
Diffstat (limited to 'src/mongo/db/pipeline/expression.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 2411 |
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"; } |
