intrusive_ptr<DocumentSource> DocumentSourceProject::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext> &pExpCtx) { /* validate */ uassert(15969, str::stream() << projectName << " specification must be an object", elem.type() == Object); Expression::ObjectCtx objectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::TOP_LEVEL | Expression::ObjectCtx::INCLUSION_OK ); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr<Expression> parsed = Expression::parseObject(elem.Obj(), &objectCtx, vps); ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(parsed.get()); massert(16402, "parseObject() returned wrong type of Expression", exprObj); uassert(16403, "$projection requires at least one output field", exprObj->getFieldCount()); intrusive_ptr<DocumentSourceProject> pProject(new DocumentSourceProject(pExpCtx, exprObj)); pProject->_variables.reset(new Variables(idGenerator.getIdCount())); BSONObj projectObj = elem.Obj(); pProject->_raw = projectObj.getOwned(); return pProject; }
intrusive_ptr<DocumentSource> DocumentSourceGroup::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext>& pExpCtx) { uassert(15947, "a group's fields must be specified in an object", elem.type() == Object); intrusive_ptr<DocumentSourceGroup> pGroup(DocumentSourceGroup::create(pExpCtx)); BSONObj groupObj(elem.Obj()); BSONObjIterator groupIterator(groupObj); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); while (groupIterator.more()) { BSONElement groupField(groupIterator.next()); const char* pFieldName = groupField.fieldName(); if (str::equals(pFieldName, "_id")) { uassert( 15948, "a group's _id may only be specified once", pGroup->_idExpressions.empty()); pGroup->parseIdExpression(groupField, vps); invariant(!pGroup->_idExpressions.empty()); } else if (str::equals(pFieldName, "$doingMerge")) { massert(17030, "$doingMerge should be true if present", groupField.Bool()); pGroup->setDoingMerge(true); } else { /* Treat as a projection field with the additional ability to add aggregation operators. */ auto parsedAccumulator = Accumulator::parseAccumulator(groupField, vps); auto fieldName = parsedAccumulator.first.toString(); auto accExpression = parsedAccumulator.second; auto factory = Accumulator::getFactory(groupField.embeddedObject().firstElementFieldName()); pGroup->addAccumulator(fieldName, factory, accExpression); } } uassert(15955, "a group specification must include an _id", !pGroup->_idExpressions.empty()); pGroup->_variables.reset(new Variables(idGenerator.getIdCount())); return pGroup; }
intrusive_ptr<DocumentSource> DocumentSourceRedact::createFromBson( BSONElement elem, const intrusive_ptr<AggregationExecContext>& expCtx) { VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); Variables::Id currentId = vps.defineVariable("CURRENT"); // will differ from ROOT Variables::Id decendId = vps.defineVariable("DESCEND"); Variables::Id pruneId = vps.defineVariable("PRUNE"); Variables::Id keepId = vps.defineVariable("KEEP"); intrusive_ptr<Expression> expression = Expression::parseOperand(elem, vps); intrusive_ptr<DocumentSourceRedact> source = new DocumentSourceRedact(expCtx, expression); // TODO figure out how much of this belongs in constructor and how much here. // Set up variables. Never need to reset DESCEND, PRUNE, or KEEP. source->_currentId = currentId; source->_variables.reset(new Variables(idGenerator.getIdCount())); source->_variables->setValue(decendId, descendVal); source->_variables->setValue(pruneId, pruneVal); source->_variables->setValue(keepId, keepVal); return source; }
intrusive_ptr<DocumentSource> DocumentSourceProject::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext> &pExpCtx) { /* validate */ uassert(15969, str::stream() << projectName << " specification must be an object", elem.type() == Object); Expression::ObjectCtx objectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::TOP_LEVEL | Expression::ObjectCtx::INCLUSION_OK ); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr<Expression> parsed = Expression::parseObject(elem.Obj(), &objectCtx, vps); ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(parsed.get()); massert(16402, "parseObject() returned wrong type of Expression", exprObj); uassert(16403, "$projection requires at least one output field", exprObj->getFieldCount()); intrusive_ptr<DocumentSourceProject> pProject(new DocumentSourceProject(pExpCtx, exprObj)); pProject->_variables.reset(new Variables(idGenerator.getIdCount())); BSONObj projectObj = elem.Obj(); pProject->_raw = projectObj.getOwned(); // probably not necessary, but better to be safe #if defined(_DEBUG) if (exprObj->isSimple()) { set<string> deps; vector<string> path; exprObj->addDependencies(deps, &path); pProject->_simpleProjection.init(depsToProjection(deps)); } #endif return pProject; }
// Check for valid replaceRoot options, and populate internal state variables. void parse(const BSONElement& spec) { // We need a VariablesParseState in order to parse the 'newRoot' expression. VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); // Get the options from this stage. Currently the only option is newRoot. for (auto&& argument : spec.Obj()) { const StringData argName = argument.fieldNameStringData(); if (argName == "newRoot") { // Allows for field path, object, and other expressions. _newRoot = Expression::parseOperand(argument, vps); } else { uasserted(40230, str::stream() << "unrecognized option to $replaceRoot stage: " << argName << ", only valid option is 'newRoot'."); } } // Check that there was a new root specified. uassert(40231, "no newRoot specified for the $replaceRoot stage", _newRoot); _variables = stdx::make_unique<Variables>(idGenerator.getIdCount()); }
intrusive_ptr<DocumentSource> DocumentSourceBucketAuto::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext>& pExpCtx) { uassert(40240, str::stream() << "The argument to $bucketAuto must be an object, but found type: " << typeName(elem.type()), elem.type() == BSONType::Object); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); vector<AccumulationStatement> accumulationStatements; boost::intrusive_ptr<Expression> groupByExpression; boost::optional<int> numBuckets; boost::intrusive_ptr<GranularityRounder> granularityRounder; for (auto&& argument : elem.Obj()) { const auto argName = argument.fieldNameStringData(); if ("groupBy" == argName) { groupByExpression = parseGroupByExpression(argument, vps); } else if ("buckets" == argName) { Value bucketsValue = Value(argument); uassert( 40241, str::stream() << "The $bucketAuto 'buckets' field must be a numeric value, but found type: " << typeName(argument.type()), bucketsValue.numeric()); uassert(40242, str::stream() << "The $bucketAuto 'buckets' field must be representable as a " "32-bit integer, but found " << Value(argument).coerceToDouble(), bucketsValue.integral()); numBuckets = bucketsValue.coerceToInt(); } else if ("output" == argName) { uassert(40244, str::stream() << "The $bucketAuto 'output' field must be an object, but found type: " << typeName(argument.type()), argument.type() == BSONType::Object); for (auto&& outputField : argument.embeddedObject()) { accumulationStatements.push_back( AccumulationStatement::parseAccumulationStatement(outputField, vps)); } } else if ("granularity" == argName) { uassert(40261, str::stream() << "The $bucketAuto 'granularity' field must be a string, but found type: " << typeName(argument.type()), argument.type() == BSONType::String); granularityRounder = GranularityRounder::getGranularityRounder(argument.str()); } else { uasserted(40245, str::stream() << "Unrecognized option to $bucketAuto: " << argName); } } uassert(40246, "$bucketAuto requires 'groupBy' and 'buckets' to be specified", groupByExpression && numBuckets); return DocumentSourceBucketAuto::create(pExpCtx, groupByExpression, idGenerator.getIdCount(), numBuckets.get(), accumulationStatements, granularityRounder); }
intrusive_ptr<DocumentSource> DocumentSourceGroup::createFromBson( BSONElement elem, const intrusive_ptr<ExpressionContext> &pExpCtx) { uassert(15947, "a group's fields must be specified in an object", elem.type() == Object); intrusive_ptr<DocumentSourceGroup> pGroup( DocumentSourceGroup::create(pExpCtx)); BSONObj groupObj(elem.Obj()); BSONObjIterator groupIterator(groupObj); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); while(groupIterator.more()) { BSONElement groupField(groupIterator.next()); const char *pFieldName = groupField.fieldName(); if (str::equals(pFieldName, "_id")) { uassert(15948, "a group's _id may only be specified once", pGroup->_idExpressions.empty()); pGroup->parseIdExpression(groupField, vps); invariant(!pGroup->_idExpressions.empty()); } else if (str::equals(pFieldName, "$doingMerge")) { massert(17030, "$doingMerge should be true if present", groupField.Bool()); pGroup->setDoingMerge(true); } else { /* Treat as a projection field with the additional ability to add aggregation operators. */ uassert(16414, str::stream() << "the group aggregate field name '" << pFieldName << "' cannot be used because $group's field names cannot contain '.'", !str::contains(pFieldName, '.') ); uassert(15950, str::stream() << "the group aggregate field name '" << pFieldName << "' cannot be an operator name", pFieldName[0] != '$'); uassert(15951, str::stream() << "the group aggregate field '" << pFieldName << "' must be defined as an expression inside an object", groupField.type() == Object); BSONObj subField(groupField.Obj()); BSONObjIterator subIterator(subField); size_t subCount = 0; for(; subIterator.more(); ++subCount) { BSONElement subElement(subIterator.next()); /* look for the specified operator */ GroupOpDesc key; key.name = subElement.fieldName(); const GroupOpDesc *pOp = (const GroupOpDesc *)bsearch( &key, GroupOpTable, NGroupOp, sizeof(GroupOpDesc), GroupOpDescCmp); uassert(15952, str::stream() << "unknown group operator '" << key.name << "'", pOp); intrusive_ptr<Expression> pGroupExpr; BSONType elementType = subElement.type(); if (elementType == Object) { Expression::ObjectCtx oCtx(Expression::ObjectCtx::DOCUMENT_OK); pGroupExpr = Expression::parseObject(subElement.Obj(), &oCtx, vps); } else if (elementType == Array) { uasserted(15953, str::stream() << "aggregating group operators are unary (" << key.name << ")"); } else { /* assume its an atomic single operand */ pGroupExpr = Expression::parseOperand(subElement, vps); } pGroup->addAccumulator(pFieldName, pOp->factory, pGroupExpr); } uassert(15954, str::stream() << "the computed aggregate '" << pFieldName << "' must specify exactly one operator", subCount == 1); } } uassert(15955, "a group specification must include an _id", !pGroup->_idExpressions.empty()); pGroup->_variables.reset(new Variables(idGenerator.getIdCount())); return pGroup; }
intrusive_ptr<DocumentSource> DocumentSourceGraphLookUp::createFromBson( BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx) { NamespaceString from; std::string as; boost::intrusive_ptr<Expression> startWith; std::string connectFromField; std::string connectToField; boost::optional<FieldPath> depthField; boost::optional<long long> maxDepth; boost::optional<BSONObj> additionalFilter; VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); for (auto&& argument : elem.Obj()) { const auto argName = argument.fieldNameStringData(); if (argName == "startWith") { startWith = Expression::parseOperand(argument, vps); continue; } else if (argName == "maxDepth") { uassert(40100, str::stream() << "maxDepth must be numeric, found type: " << typeName(argument.type()), argument.isNumber()); maxDepth = argument.safeNumberLong(); uassert(40101, str::stream() << "maxDepth requires a nonnegative argument, found: " << *maxDepth, *maxDepth >= 0); uassert(40102, str::stream() << "maxDepth could not be represented as a long long: " << *maxDepth, *maxDepth == argument.number()); continue; } else if (argName == "restrictSearchWithMatch") { uassert(40185, str::stream() << "restrictSearchWithMatch must be an object, found " << typeName(argument.type()), argument.type() == Object); // We don't need to keep ahold of the MatchExpression, but we do need to ensure that // the specified object is parseable. auto parsedMatchExpression = MatchExpressionParser::parse( argument.embeddedObject(), ExtensionsCallbackDisallowExtensions(), nullptr); uassert(40186, str::stream() << "Failed to parse 'restrictSearchWithMatch' option to $graphLookup: " << parsedMatchExpression.getStatus().reason(), parsedMatchExpression.isOK()); uassert(40187, str::stream() << "Failed to parse 'restrictSearchWithMatch' option to $graphLookup: " << "$near not supported.", !QueryPlannerCommon::hasNode(parsedMatchExpression.getValue().get(), MatchExpression::GEO_NEAR)); additionalFilter = argument.embeddedObject().getOwned(); continue; } if (argName == "from" || argName == "as" || argName == "connectFromField" || argName == "depthField" || argName == "connectToField") { // All remaining arguments to $graphLookup are expected to be strings. uassert(40103, str::stream() << "expected string as argument for " << argName << ", found: " << argument.toString(false, false), argument.type() == String); } if (argName == "from") { from = NamespaceString(expCtx->ns.db().toString() + '.' + argument.String()); } else if (argName == "as") { as = argument.String(); } else if (argName == "connectFromField") { connectFromField = argument.String(); } else if (argName == "connectToField") { connectToField = argument.String(); } else if (argName == "depthField") { depthField = boost::optional<FieldPath>(FieldPath(argument.String())); } else { uasserted(40104, str::stream() << "Unknown argument to $graphLookup: " << argument.fieldName()); } } const bool isMissingRequiredField = from.ns().empty() || as.empty() || !startWith || connectFromField.empty() || connectToField.empty(); uassert(40105, str::stream() << "$graphLookup requires 'from', 'as', 'startWith', 'connectFromField', " << "and 'connectToField' to be specified.", !isMissingRequiredField); intrusive_ptr<DocumentSourceGraphLookUp> newSource( new DocumentSourceGraphLookUp(std::move(from), std::move(as), std::move(connectFromField), std::move(connectToField), std::move(startWith), additionalFilter, depthField, maxDepth, expCtx)); newSource->_variables.reset(new Variables(idGenerator.getIdCount())); return std::move(newSource); }
intrusive_ptr<DocumentSource> DocumentSourceGraphLookUp::createFromBson( BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx) { NamespaceString from; std::string as; boost::intrusive_ptr<Expression> startWith; std::string connectFromField; std::string connectToField; boost::optional<FieldPath> depthField; boost::optional<long long> maxDepth; VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); for (auto&& argument : elem.Obj()) { const auto argName = argument.fieldNameStringData(); if (argName == "startWith") { startWith = Expression::parseOperand(argument, vps); continue; } else if (argName == "maxDepth") { uassert(40100, str::stream() << "maxDepth must be numeric, found type: " << typeName(argument.type()), argument.isNumber()); maxDepth = argument.safeNumberLong(); uassert(40101, str::stream() << "maxDepth requires a nonnegative argument, found: " << *maxDepth, *maxDepth >= 0); uassert(40102, str::stream() << "maxDepth could not be represented as a long long: " << *maxDepth, *maxDepth == argument.number()); continue; } if (argName == "from" || argName == "as" || argName == "connectFromField" || argName == "depthField" || argName == "connectToField") { // All remaining arguments to $graphLookup are expected to be strings. uassert(40103, str::stream() << "expected string as argument for " << argName << ", found: " << argument.toString(false, false), argument.type() == String); } if (argName == "from") { from = NamespaceString(expCtx->ns.db().toString() + '.' + argument.String()); } else if (argName == "as") { as = argument.String(); } else if (argName == "connectFromField") { connectFromField = argument.String(); } else if (argName == "connectToField") { connectToField = argument.String(); } else if (argName == "depthField") { depthField = boost::optional<FieldPath>(FieldPath(argument.String())); } else { uasserted(40104, str::stream() << "Unknown argument to $graphLookup: " << argument.fieldName()); } } const bool isMissingRequiredField = from.ns().empty() || as.empty() || !startWith || connectFromField.empty() || connectToField.empty(); uassert(40105, str::stream() << "$graphLookup requires 'from', 'as', 'startWith', 'connectFromField', " << "and 'connectToField' to be specified.", !isMissingRequiredField); intrusive_ptr<DocumentSourceGraphLookUp> newSource( new DocumentSourceGraphLookUp(std::move(from), std::move(as), std::move(connectFromField), std::move(connectToField), std::move(startWith), depthField, maxDepth, expCtx)); newSource->_variables.reset(new Variables(idGenerator.getIdCount())); return std::move(newSource); }