// static void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields) { // The _id is included by default. bool includeId = true; // Figure out what fields are in the projection. TODO: we can get this from the // ParsedProjection...modify that to have this type instead of a vector. BSONObjIterator projObjIt(projObj); while (projObjIt.more()) { BSONElement elt = projObjIt.next(); // Must deal with the _id case separately as there is an implicit _id: 1 in the // projection. if (mongoutils::str::equals(elt.fieldName(), kIdField) && !elt.trueValue()) { includeId = false; continue; } includedFields->insert(elt.fieldNameStringData()); } if (includeId) { includedFields->insert(kIdField); } }
Status Command::getStatusFromCommandResult(const BSONObj& result) { BSONElement okElement = result["ok"]; BSONElement codeElement = result["code"]; BSONElement errmsgElement = result["errmsg"]; if (okElement.eoo()) { return Status(ErrorCodes::CommandResultSchemaViolation, mongoutils::str::stream() << "No \"ok\" field in command result " << result); } if (okElement.trueValue()) { return Status::OK(); } int code = codeElement.numberInt(); if (0 == code) code = ErrorCodes::UnknownError; std::string errmsg; if (errmsgElement.type() == String) { errmsg = errmsgElement.String(); } else if (!errmsgElement.eoo()) { errmsg = errmsgElement.toString(); } return Status(ErrorCodes::Error(code), errmsg); }
StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, const char* name, const BSONElement& e, int position, bool* stop ) { *stop = false; // TODO: these should move to getGtLtOp, or its replacement if ( mongoutils::str::equals( "$eq", e.fieldName() ) ) return _parseComparison( name, new EqualityMatchExpression(), e ); if ( mongoutils::str::equals( "$not", e.fieldName() ) ) { return _parseNot( name, e ); } int x = e.getGtLtOp(-1); switch ( x ) { case -1: return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "unknown operator: " << e.fieldName() ); case BSONObj::LT: return _parseComparison( name, new LTMatchExpression(), e ); case BSONObj::LTE: return _parseComparison( name, new LTEMatchExpression(), e ); case BSONObj::GT: return _parseComparison( name, new GTMatchExpression(), e ); case BSONObj::GTE: return _parseComparison( name, new GTEMatchExpression(), e ); case BSONObj::NE: return _parseComparison( name, new NEMatchExpression(), e ); case BSONObj::Equality: return _parseComparison( name, new EqualityMatchExpression(), e ); case BSONObj::opIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); std::auto_ptr<InMatchExpression> temp( new InMatchExpression() ); temp->init( name ); Status s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::NIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$nin needs an array" ); std::auto_ptr<NinMatchExpression> temp( new NinMatchExpression() ); temp->init( name ); Status s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opSIZE: { int size = 0; if ( e.type() == String ) { // matching old odd semantics size = 0; } else if ( e.type() == NumberInt || e.type() == NumberLong ) { size = e.numberInt(); } else if ( e.type() == NumberDouble ) { if ( e.numberInt() == e.numberDouble() ) { size = e.numberInt(); } else { // old semantcs require exact numeric match // so [1,2] != 1 or 2 size = -1; } } else { return StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); } std::auto_ptr<SizeMatchExpression> temp( new SizeMatchExpression() ); Status s = temp->init( name, size ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opEXISTS: { if ( e.eoo() ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$exists can't be eoo" ); std::auto_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); Status s = temp->init( name, e.trueValue() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opTYPE: { if ( !e.isNumber() ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$type has to be a number" ); int type = e.numberInt(); if ( e.type() != NumberInt && type != e.number() ) type = -1; std::auto_ptr<TypeMatchExpression> temp( new TypeMatchExpression() ); Status s = temp->init( name, type ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opMOD: return _parseMOD( name, e ); case BSONObj::opOPTIONS: return StatusWithMatchExpression( ErrorCodes::BadValue, "$options has to be after a $regex" ); case BSONObj::opREGEX: { if ( position != 0 ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$regex has to be first" ); *stop = true; return _parseRegexDocument( name, context ); } case BSONObj::opELEM_MATCH: return _parseElemMatch( name, e ); case BSONObj::opALL: return _parseAll( name, e ); case BSONObj::opWITHIN: return expressionParserGeoCallback( name, context ); default: return StatusWithMatchExpression( ErrorCodes::BadValue, "not done" ); } }
ProjectionExec::ProjectionExec(const BSONObj& spec, const MatchExpression* queryExpression, const CollatorInterface* collator, const ExtensionsCallback& extensionsCallback) : _include(true), _special(false), _source(spec), _includeID(true), _skip(0), _limit(-1), _arrayOpType(ARRAY_OP_NORMAL), _queryExpression(queryExpression), _hasReturnKey(false), _collator(collator) { // Whether we're including or excluding fields. enum class IncludeExclude { kUninitialized, kInclude, kExclude }; IncludeExclude includeExclude = IncludeExclude::kUninitialized; BSONObjIterator it(_source); while (it.more()) { BSONElement e = it.next(); if (Object == e.type()) { BSONObj obj = e.embeddedObject(); verify(1 == obj.nFields()); BSONElement e2 = obj.firstElement(); if (mongoutils::str::equals(e2.fieldName(), "$slice")) { if (e2.isNumber()) { int i = e2.numberInt(); if (i < 0) { add(e.fieldName(), i, -i); // limit is now positive } else { add(e.fieldName(), 0, i); } } else { verify(e2.type() == Array); BSONObj arr = e2.embeddedObject(); verify(2 == arr.nFields()); BSONObjIterator it(arr); int skip = it.next().numberInt(); int limit = it.next().numberInt(); verify(limit > 0); add(e.fieldName(), skip, limit); } } else if (mongoutils::str::equals(e2.fieldName(), "$elemMatch")) { _arrayOpType = ARRAY_OP_ELEM_MATCH; // Create a MatchExpression for the elemMatch. BSONObj elemMatchObj = e.wrap(); verify(elemMatchObj.isOwned()); _elemMatchObjs.push_back(elemMatchObj); StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(elemMatchObj, extensionsCallback, _collator); verify(statusWithMatcher.isOK()); // And store it in _matchers. _matchers[mongoutils::str::before(e.fieldName(), '.').c_str()] = statusWithMatcher.getValue().release(); add(e.fieldName(), true); } else if (mongoutils::str::equals(e2.fieldName(), "$meta")) { verify(String == e2.type()); if (e2.valuestr() == QueryRequest::metaTextScore) { _meta[e.fieldName()] = META_TEXT_SCORE; } else if (e2.valuestr() == QueryRequest::metaSortKey) { _sortKeyMetaFields.push_back(e.fieldName()); _meta[_sortKeyMetaFields.back()] = META_SORT_KEY; } else if (e2.valuestr() == QueryRequest::metaRecordId) { _meta[e.fieldName()] = META_RECORDID; } else if (e2.valuestr() == QueryRequest::metaGeoNearPoint) { _meta[e.fieldName()] = META_GEONEAR_POINT; } else if (e2.valuestr() == QueryRequest::metaGeoNearDistance) { _meta[e.fieldName()] = META_GEONEAR_DIST; } else if (e2.valuestr() == QueryRequest::metaIndexKey) { _hasReturnKey = true; } else { // This shouldn't happen, should be caught by parsing. verify(0); } } else { verify(0); } } else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; } else { add(e.fieldName(), e.trueValue()); // If we haven't specified an include/exclude, initialize includeExclude. if (includeExclude == IncludeExclude::kUninitialized) { includeExclude = e.trueValue() ? IncludeExclude::kInclude : IncludeExclude::kExclude; _include = !e.trueValue(); } } if (mongoutils::str::contains(e.fieldName(), ".$")) { _arrayOpType = ARRAY_OP_POSITIONAL; } } }
StatusWithMatchExpression MatchExpressionParser::_parse( const BSONObj& obj, bool topLevel ) { std::auto_ptr<AndMatchExpression> root( new AndMatchExpression() ); BSONObjIterator i( obj ); while ( i.more() ){ BSONElement e = i.next(); if ( e.fieldName()[0] == '$' ) { const char * rest = e.fieldName() + 1; // TODO: optimize if block? if ( mongoutils::str::equals( "or", rest ) ) { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$or needs an array" ); std::auto_ptr<OrMatchExpression> temp( new OrMatchExpression() ); Status s = _parseTreeList( e.Obj(), temp.get() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); root->add( temp.release() ); } else if ( mongoutils::str::equals( "and", rest ) ) { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "and needs an array" ); std::auto_ptr<AndMatchExpression> temp( new AndMatchExpression() ); Status s = _parseTreeList( e.Obj(), temp.get() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); root->add( temp.release() ); } else if ( mongoutils::str::equals( "nor", rest ) ) { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "and needs an array" ); std::auto_ptr<NorMatchExpression> temp( new NorMatchExpression() ); Status s = _parseTreeList( e.Obj(), temp.get() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); root->add( temp.release() ); } else if ( mongoutils::str::equals( "atomic", rest ) || mongoutils::str::equals( "isolated", rest ) ) { if ( !topLevel ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$atomic/$isolated has to be at the top level" ); if ( e.trueValue() ) root->add( new AtomicMatchExpression() ); } else if ( mongoutils::str::equals( "where", rest ) ) { /* if ( !topLevel ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$where has to be at the top level" ); */ StatusWithMatchExpression s = expressionParserWhereCallback( e ); if ( !s.isOK() ) return s; root->add( s.getValue() ); } else if ( mongoutils::str::equals( "text", rest ) ) { if ( e.type() != Object ) { return StatusWithMatchExpression( ErrorCodes::BadValue, "$text expects an object" ); } StatusWithMatchExpression s = expressionParserTextCallback( e.Obj() ); if ( !s.isOK() ) { return s; } root->add( s.getValue() ); } else if ( mongoutils::str::equals( "comment", rest ) ) { } else { return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "unknown top level operator: " << e.fieldName() ); } continue; } if ( _isExpressionDocument( e ) ) { Status s = _parseSub( e.fieldName(), e.Obj(), root.get() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); continue; } if ( e.type() == RegEx ) { StatusWithMatchExpression result = _parseRegexElement( e.fieldName(), e ); if ( !result.isOK() ) return result; root->add( result.getValue() ); continue; } std::auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); Status s = eq->init( e.fieldName(), e ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); root->add( eq.release() ); } if ( root->numChildren() == 1 ) { const MatchExpression* real = root->getChild(0); root->clearAndRelease(); return StatusWithMatchExpression( const_cast<MatchExpression*>(real) ); } return StatusWithMatchExpression( root.release() ); }
// static bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, const QuerySolutionNode* trueSoln) { // // leaf nodes // if (STAGE_COLLSCAN == trueSoln->getType()) { const CollectionScanNode* csn = static_cast<const CollectionScanNode*>(trueSoln); BSONElement el = testSoln["cscan"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj csObj = el.Obj(); BSONElement dir = csObj["dir"]; if (dir.eoo() || !dir.isNumber()) { return false; } if (dir.numberInt() != csn->direction) { return false; } BSONElement filter = csObj["filter"]; if (filter.eoo()) { return true; } else if (filter.isNull()) { return NULL == csn->filter; } else if (!filter.isABSONObj()) { return false; } BSONObj collation; if (BSONElement collationElt = csObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } return filterMatches(filter.Obj(), collation, trueSoln); } else if (STAGE_IXSCAN == trueSoln->getType()) { const IndexScanNode* ixn = static_cast<const IndexScanNode*>(trueSoln); BSONElement el = testSoln["ixscan"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj ixscanObj = el.Obj(); BSONElement pattern = ixscanObj["pattern"]; if (pattern.eoo() || !pattern.isABSONObj()) { return false; } if (pattern.Obj() != ixn->indexKeyPattern) { return false; } BSONElement bounds = ixscanObj["bounds"]; if (!bounds.eoo()) { if (!bounds.isABSONObj()) { return false; } else if (!boundsMatch(bounds.Obj(), ixn->bounds)) { return false; } } BSONElement dir = ixscanObj["dir"]; if (!dir.eoo() && NumberInt == dir.type()) { if (dir.numberInt() != ixn->direction) { return false; } } BSONElement filter = ixscanObj["filter"]; if (filter.eoo()) { return true; } else if (filter.isNull()) { return NULL == ixn->filter; } else if (!filter.isABSONObj()) { return false; } BSONObj collation; if (BSONElement collationElt = ixscanObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } return filterMatches(filter.Obj(), collation, trueSoln); } else if (STAGE_GEO_NEAR_2D == trueSoln->getType()) { const GeoNear2DNode* node = static_cast<const GeoNear2DNode*>(trueSoln); BSONElement el = testSoln["geoNear2d"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj geoObj = el.Obj(); return geoObj == node->indexKeyPattern; } else if (STAGE_GEO_NEAR_2DSPHERE == trueSoln->getType()) { const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(trueSoln); BSONElement el = testSoln["geoNear2dsphere"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj geoObj = el.Obj(); BSONElement pattern = geoObj["pattern"]; if (pattern.eoo() || !pattern.isABSONObj()) { return false; } if (pattern.Obj() != node->indexKeyPattern) { return false; } BSONElement bounds = geoObj["bounds"]; if (!bounds.eoo()) { if (!bounds.isABSONObj()) { return false; } else if (!boundsMatch(bounds.Obj(), node->baseBounds)) { return false; } } return true; } else if (STAGE_TEXT == trueSoln->getType()) { // {text: {search: "somestr", language: "something", filter: {blah: 1}}} const TextNode* node = static_cast<const TextNode*>(trueSoln); BSONElement el = testSoln["text"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj textObj = el.Obj(); BSONElement searchElt = textObj["search"]; if (!searchElt.eoo()) { if (searchElt.String() != node->ftsQuery->getQuery()) { return false; } } BSONElement languageElt = textObj["language"]; if (!languageElt.eoo()) { if (languageElt.String() != node->ftsQuery->getLanguage()) { return false; } } BSONElement caseSensitiveElt = textObj["caseSensitive"]; if (!caseSensitiveElt.eoo()) { if (caseSensitiveElt.trueValue() != node->ftsQuery->getCaseSensitive()) { return false; } } BSONElement diacriticSensitiveElt = textObj["diacriticSensitive"]; if (!diacriticSensitiveElt.eoo()) { if (diacriticSensitiveElt.trueValue() != node->ftsQuery->getDiacriticSensitive()) { return false; } } BSONElement indexPrefix = textObj["prefix"]; if (!indexPrefix.eoo()) { if (!indexPrefix.isABSONObj()) { return false; } if (0 != indexPrefix.Obj().woCompare(node->indexPrefix)) { return false; } } BSONObj collation; if (BSONElement collationElt = textObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } BSONElement filter = textObj["filter"]; if (!filter.eoo()) { if (filter.isNull()) { if (NULL != node->filter) { return false; } } else if (!filter.isABSONObj()) { return false; } else if (!filterMatches(filter.Obj(), collation, trueSoln)) { return false; } } return true; } // // internal nodes // if (STAGE_FETCH == trueSoln->getType()) { const FetchNode* fn = static_cast<const FetchNode*>(trueSoln); BSONElement el = testSoln["fetch"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj fetchObj = el.Obj(); BSONObj collation; if (BSONElement collationElt = fetchObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } BSONElement filter = fetchObj["filter"]; if (!filter.eoo()) { if (filter.isNull()) { if (NULL != fn->filter) { return false; } } else if (!filter.isABSONObj()) { return false; } else if (!filterMatches(filter.Obj(), collation, trueSoln)) { return false; } } BSONElement child = fetchObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return solutionMatches(child.Obj(), fn->children[0]); } else if (STAGE_OR == trueSoln->getType()) { const OrNode* orn = static_cast<const OrNode*>(trueSoln); BSONElement el = testSoln["or"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj orObj = el.Obj(); return childrenMatch(orObj, orn); } else if (STAGE_AND_HASH == trueSoln->getType()) { const AndHashNode* ahn = static_cast<const AndHashNode*>(trueSoln); BSONElement el = testSoln["andHash"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj andHashObj = el.Obj(); BSONObj collation; if (BSONElement collationElt = andHashObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } BSONElement filter = andHashObj["filter"]; if (!filter.eoo()) { if (filter.isNull()) { if (NULL != ahn->filter) { return false; } } else if (!filter.isABSONObj()) { return false; } else if (!filterMatches(filter.Obj(), collation, trueSoln)) { return false; } } return childrenMatch(andHashObj, ahn); } else if (STAGE_AND_SORTED == trueSoln->getType()) { const AndSortedNode* asn = static_cast<const AndSortedNode*>(trueSoln); BSONElement el = testSoln["andSorted"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj andSortedObj = el.Obj(); BSONObj collation; if (BSONElement collationElt = andSortedObj["collation"]) { if (!collationElt.isABSONObj()) { return false; } collation = collationElt.Obj(); } BSONElement filter = andSortedObj["filter"]; if (!filter.eoo()) { if (filter.isNull()) { if (NULL != asn->filter) { return false; } } else if (!filter.isABSONObj()) { return false; } else if (!filterMatches(filter.Obj(), collation, trueSoln)) { return false; } } return childrenMatch(andSortedObj, asn); } else if (STAGE_PROJECTION == trueSoln->getType()) { const ProjectionNode* pn = static_cast<const ProjectionNode*>(trueSoln); BSONElement el = testSoln["proj"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj projObj = el.Obj(); BSONElement projType = projObj["type"]; if (!projType.eoo()) { string projTypeStr = projType.str(); if (!((pn->projType == ProjectionNode::DEFAULT && projTypeStr == "default") || (pn->projType == ProjectionNode::SIMPLE_DOC && projTypeStr == "simple") || (pn->projType == ProjectionNode::COVERED_ONE_INDEX && projTypeStr == "coveredIndex"))) { return false; } } BSONElement spec = projObj["spec"]; if (spec.eoo() || !spec.isABSONObj()) { return false; } BSONElement child = projObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return (spec.Obj() == pn->projection) && solutionMatches(child.Obj(), pn->children[0]); } else if (STAGE_SORT == trueSoln->getType()) { const SortNode* sn = static_cast<const SortNode*>(trueSoln); BSONElement el = testSoln["sort"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj sortObj = el.Obj(); BSONElement patternEl = sortObj["pattern"]; if (patternEl.eoo() || !patternEl.isABSONObj()) { return false; } BSONElement limitEl = sortObj["limit"]; if (!limitEl.isNumber()) { return false; } BSONElement child = sortObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } size_t expectedLimit = limitEl.numberInt(); return (patternEl.Obj() == sn->pattern) && (expectedLimit == sn->limit) && solutionMatches(child.Obj(), sn->children[0]); } else if (STAGE_SORT_KEY_GENERATOR == trueSoln->getType()) { const SortKeyGeneratorNode* keyGenNode = static_cast<const SortKeyGeneratorNode*>(trueSoln); BSONElement el = testSoln["sortKeyGen"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj keyGenObj = el.Obj(); BSONElement child = keyGenObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return solutionMatches(child.Obj(), keyGenNode->children[0]); } else if (STAGE_SORT_MERGE == trueSoln->getType()) { const MergeSortNode* msn = static_cast<const MergeSortNode*>(trueSoln); BSONElement el = testSoln["mergeSort"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj mergeSortObj = el.Obj(); return childrenMatch(mergeSortObj, msn); } else if (STAGE_SKIP == trueSoln->getType()) { const SkipNode* sn = static_cast<const SkipNode*>(trueSoln); BSONElement el = testSoln["skip"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj sortObj = el.Obj(); BSONElement skipEl = sortObj["n"]; if (!skipEl.isNumber()) { return false; } BSONElement child = sortObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return (skipEl.numberInt() == sn->skip) && solutionMatches(child.Obj(), sn->children[0]); } else if (STAGE_LIMIT == trueSoln->getType()) { const LimitNode* ln = static_cast<const LimitNode*>(trueSoln); BSONElement el = testSoln["limit"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj sortObj = el.Obj(); BSONElement limitEl = sortObj["n"]; if (!limitEl.isNumber()) { return false; } BSONElement child = sortObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return (limitEl.numberInt() == ln->limit) && solutionMatches(child.Obj(), ln->children[0]); } else if (STAGE_KEEP_MUTATIONS == trueSoln->getType()) { const KeepMutationsNode* kn = static_cast<const KeepMutationsNode*>(trueSoln); BSONElement el = testSoln["keep"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj keepObj = el.Obj(); // Doesn't have any parameters really. BSONElement child = keepObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return solutionMatches(child.Obj(), kn->children[0]); } else if (STAGE_SHARDING_FILTER == trueSoln->getType()) { const ShardingFilterNode* fn = static_cast<const ShardingFilterNode*>(trueSoln); BSONElement el = testSoln["sharding_filter"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj keepObj = el.Obj(); BSONElement child = keepObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return solutionMatches(child.Obj(), fn->children[0]); } else if (STAGE_ENSURE_SORTED == trueSoln->getType()) { const EnsureSortedNode* esn = static_cast<const EnsureSortedNode*>(trueSoln); BSONElement el = testSoln["ensureSorted"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj esObj = el.Obj(); BSONElement patternEl = esObj["pattern"]; if (patternEl.eoo() || !patternEl.isABSONObj()) { return false; } BSONElement child = esObj["node"]; if (child.eoo() || !child.isABSONObj()) { return false; } return (patternEl.Obj() == esn->pattern) && solutionMatches(child.Obj(), esn->children[0]); } return false; }
Status Collection::validate(OperationContext* txn, bool full, bool scanData, ValidateResults* results, BSONObjBuilder* output) { dassert(txn->lockState()->isCollectionLockedForMode(ns().toString(), MODE_IS)); MyValidateAdaptor adaptor; Status status = _recordStore->validate(txn, full, scanData, &adaptor, results, output); if (!status.isOK()) return status; { // indexes output->append("nIndexes", _indexCatalog.numIndexesReady(txn)); int idxn = 0; try { // Only applicable when 'full' validation is requested. std::unique_ptr<BSONObjBuilder> indexDetails(full ? new BSONObjBuilder() : NULL); BSONObjBuilder indexes; // not using subObjStart to be exception safe IndexCatalog::IndexIterator i = _indexCatalog.getIndexIterator(txn, false); while (i.more()) { const IndexDescriptor* descriptor = i.next(); log(LogComponent::kIndex) << "validating index " << descriptor->indexNamespace() << endl; IndexAccessMethod* iam = _indexCatalog.getIndex(descriptor); invariant(iam); std::unique_ptr<BSONObjBuilder> bob( indexDetails.get() ? new BSONObjBuilder(indexDetails->subobjStart( descriptor->indexNamespace())) : NULL); int64_t keys; iam->validate(txn, full, &keys, bob.get()); indexes.appendNumber(descriptor->indexNamespace(), static_cast<long long>(keys)); validateIndexKeyCount( txn, *descriptor, keys, _recordStore->numRecords(txn), results); if (bob) { BSONObj obj = bob->done(); BSONElement valid = obj["valid"]; if (valid.ok() && !valid.trueValue()) { results->valid = false; } } idxn++; } output->append("keysPerIndex", indexes.done()); if (indexDetails.get()) { output->append("indexDetails", indexDetails->done()); } } catch (DBException& exc) { string err = str::stream() << "exception during index validate idxn " << BSONObjBuilder::numStr(idxn) << ": " << exc.toString(); results->errors.push_back(err); results->valid = false; } } return Status::OK(); }
Status LiteParsedQuery::initFullQuery(const BSONObj& top) { BSONObjIterator i(top); while (i.more()) { BSONElement e = i.next(); const char* name = e.fieldName(); if (0 == strcmp("$orderby", name) || 0 == strcmp("orderby", name)) { if (Object == e.type()) { _sort = e.embeddedObject(); } else if (Array == e.type()) { _sort = e.embeddedObject(); // TODO: Is this ever used? I don't think so. // Quote: // This is for languages whose "objects" are not well ordered (JSON is well // ordered). // [ { a : ... } , { b : ... } ] -> { a : ..., b : ... } // note: this is slow, but that is ok as order will have very few pieces BSONObjBuilder b; char p[2] = "0"; while (1) { BSONObj j = _sort.getObjectField(p); if (j.isEmpty()) { break; } BSONElement e = j.firstElement(); if (e.eoo()) { return Status(ErrorCodes::BadValue, "bad order array"); } if (!e.isNumber()) { return Status(ErrorCodes::BadValue, "bad order array [2]"); } b.append(e); (*p)++; if (!(*p <= '9')) { return Status(ErrorCodes::BadValue, "too many ordering elements"); } } _sort = b.obj(); } else { return Status(ErrorCodes::BadValue, "sort must be object or array"); } } else if ('$' == *name) { name++; if (str::equals("explain", name)) { // Won't throw. _explain = e.trueValue(); } else if (str::equals("snapshot", name)) { // Won't throw. _snapshot = e.trueValue(); } else if (str::equals("min", name)) { if (!e.isABSONObj()) { return Status(ErrorCodes::BadValue, "$min must be a BSONObj"); } _min = e.embeddedObject(); } else if (str::equals("max", name)) { if (!e.isABSONObj()) { return Status(ErrorCodes::BadValue, "$max must be a BSONObj"); } _max = e.embeddedObject(); } else if (str::equals("hint", name)) { if (e.isABSONObj()) { _hint = e.embeddedObject(); } else { // Hint can be specified as an object or as a string. Wrap takes care of // it. _hint = e.wrap(); } } else if (str::equals("returnKey", name)) { // Won't throw. _returnKey = e.trueValue(); } else if (str::equals("maxScan", name)) { // Won't throw. _maxScan = e.numberInt(); } else if (str::equals("showDiskLoc", name)) { // Won't throw. _showDiskLoc = e.trueValue(); } else if (str::equals("maxTimeMS", name)) { StatusWith<int> maxTimeMS = parseMaxTimeMS(e); if (!maxTimeMS.isOK()) { return maxTimeMS.getStatus(); } _maxTimeMS = maxTimeMS.getValue(); } } } if (_snapshot) { if (!_sort.isEmpty()) { return Status(ErrorCodes::BadValue, "E12001 can't use sort with $snapshot"); } if (!_hint.isEmpty()) { return Status(ErrorCodes::BadValue, "E12002 can't use hint with $snapshot"); } } return Status::OK(); }
ProjectionExec::ProjectionExec(const BSONObj& spec, const MatchExpression* queryExpression, const MatchExpressionParser::WhereCallback& whereCallback) : _include(true), _special(false), _source(spec), _includeID(true), _skip(0), _limit(-1), _arrayOpType(ARRAY_OP_NORMAL), _hasNonSimple(false), _hasDottedField(false), _queryExpression(queryExpression), _hasReturnKey(false) { // Are we including or excluding fields? // -1 when we haven't initialized it. // 1 when we're including // 0 when we're excluding. int include_exclude = -1; BSONObjIterator it(_source); while (it.more()) { BSONElement e = it.next(); if (!e.isNumber() && !e.isBoolean()) { _hasNonSimple = true; } if (Object == e.type()) { BSONObj obj = e.embeddedObject(); verify(1 == obj.nFields()); BSONElement e2 = obj.firstElement(); if (mongoutils::str::equals(e2.fieldName(), "$slice")) { if (e2.isNumber()) { int i = e2.numberInt(); if (i < 0) { add(e.fieldName(), i, -i); // limit is now positive } else { add(e.fieldName(), 0, i); } } else { verify(e2.type() == Array); BSONObj arr = e2.embeddedObject(); verify(2 == arr.nFields()); BSONObjIterator it(arr); int skip = it.next().numberInt(); int limit = it.next().numberInt(); verify(limit > 0); add(e.fieldName(), skip, limit); } } else if (mongoutils::str::equals(e2.fieldName(), "$elemMatch")) { _arrayOpType = ARRAY_OP_ELEM_MATCH; // Create a MatchExpression for the elemMatch. BSONObj elemMatchObj = e.wrap(); verify(elemMatchObj.isOwned()); _elemMatchObjs.push_back(elemMatchObj); StatusWithMatchExpression swme = MatchExpressionParser::parse(elemMatchObj, whereCallback); verify(swme.isOK()); // And store it in _matchers. _matchers[mongoutils::str::before(e.fieldName(), '.').c_str()] = swme.getValue(); add(e.fieldName(), true); } else if (mongoutils::str::equals(e2.fieldName(), "$meta")) { verify(String == e2.type()); if (e2.valuestr() == LiteParsedQuery::metaTextScore) { _meta[e.fieldName()] = META_TEXT_SCORE; } else if (e2.valuestr() == LiteParsedQuery::metaRecordId) { _meta[e.fieldName()] = META_RECORDID; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearPoint) { _meta[e.fieldName()] = META_GEONEAR_POINT; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearDistance) { _meta[e.fieldName()] = META_GEONEAR_DIST; } else if (e2.valuestr() == LiteParsedQuery::metaIndexKey) { _hasReturnKey = true; // The index key clobbers everything so just stop parsing here. return; } else { // This shouldn't happen, should be caught by parsing. verify(0); } } else { verify(0); } } else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; } else { add(e.fieldName(), e.trueValue()); // Projections of dotted fields aren't covered. if (mongoutils::str::contains(e.fieldName(), '.')) { _hasDottedField = true; } // Validate input. if (include_exclude == -1) { // If we haven't specified an include/exclude, initialize include_exclude. // We expect further include/excludes to match it. include_exclude = e.trueValue(); _include = !e.trueValue(); } } if (mongoutils::str::contains(e.fieldName(), ".$")) { _arrayOpType = ARRAY_OP_POSITIONAL; } } }
Status CollectionOptions::parse(const BSONObj& options) { reset(); // During parsing, ignore some validation errors in order to accept options objects that // were valid in previous versions of the server. SERVER-13737. BSONObjIterator i( options ); while ( i.more() ) { BSONElement e = i.next(); StringData fieldName = e.fieldName(); if ( fieldName == "capped" ) { capped = e.trueValue(); } else if ( fieldName == "size" ) { if ( !e.isNumber() ) { // Ignoring for backwards compatibility. continue; } cappedSize = e.numberLong(); if ( cappedSize < 0 ) return Status( ErrorCodes::BadValue, "size has to be >= 0" ); cappedSize += 0xff; cappedSize &= 0xffffffffffffff00LL; } else if ( fieldName == "max" ) { if ( !options["capped"].trueValue() || !e.isNumber() ) { // Ignoring for backwards compatibility. continue; } cappedMaxDocs = e.numberLong(); if ( !validMaxCappedDocs( &cappedMaxDocs ) ) return Status( ErrorCodes::BadValue, "max in a capped collection has to be < 2^31 or not set" ); } else if ( fieldName == "$nExtents" ) { if ( e.type() == Array ) { BSONObjIterator j( e.Obj() ); while ( j.more() ) { BSONElement inner = j.next(); initialExtentSizes.push_back( inner.numberInt() ); } } else { initialNumExtents = e.numberLong(); } } else if ( fieldName == "autoIndexId" ) { if ( e.trueValue() ) autoIndexId = YES; else autoIndexId = NO; } else if ( fieldName == "flags" ) { flags = e.numberInt(); flagsSet = true; } else if ( fieldName == "temp" ) { temp = e.trueValue(); } else if (fieldName == "storageEngine") { // Storage engine-specific collection options. // "storageEngine" field must be of type "document". // Every field inside "storageEngine" has to be a document. // Format: // { // ... // storageEngine: { // storageEngine1: { // ... // }, // storageEngine2: { // ... // } // }, // ... // } if (e.type() != mongo::Object) { return Status(ErrorCodes::BadValue, "'storageEngine' has to be a document."); } BSONObjIterator j(e.Obj()); if (!j.more()) { return Status(ErrorCodes::BadValue, "Empty 'storageEngine' options are invalid. " "Please remove, or include valid options."); } // Loop through each provided storageEngine. while (j.more()) { BSONElement storageEngineElement = j.next(); StringData storageEngineName = storageEngineElement.fieldNameStringData(); if (storageEngineElement.type() != mongo::Object) { return Status(ErrorCodes::BadValue, str::stream() << "'storageEngine." << storageEngineName << "' has to be an embedded document."); } } storageEngine = e.Obj().getOwned(); } } return Status::OK(); }
void ParsedQuery::_initTop( const BSONObj& top ) { BSONObjIterator i( top ); while ( i.more() ) { BSONElement e = i.next(); const char * name = e.fieldName(); if ( strcmp( "$orderby" , name ) == 0 || strcmp( "orderby" , name ) == 0 ) { if ( e.type() == Object ) { _order = e.embeddedObject(); } else if ( e.type() == Array ) { _order = transformOrderFromArrayFormat( _order ); } else { uasserted(13513, "sort must be an object or array"); } continue; } if( *name == '$' ) { name++; if ( strcmp( "explain" , name ) == 0 ) { _explain = e.trueValue(); } else if ( strcmp( "snapshot" , name ) == 0 ) { _snapshot = e.trueValue(); } else if ( strcmp( "min" , name ) == 0 ) { _min = e.embeddedObject(); } else if ( strcmp( "max" , name ) == 0 ) { _max = e.embeddedObject(); } else if ( strcmp( "hint" , name ) == 0 ) { _hint = e.wrap(); } else if ( strcmp( "returnKey" , name ) == 0 ) { _returnKey = e.trueValue(); } else if ( strcmp( "maxScan" , name ) == 0 ) { _maxScan = e.numberInt(); } else if ( strcmp( "showDiskLoc" , name ) == 0 ) { _showDiskLoc = e.trueValue(); } else if ( strcmp( "maxTimeMS" , name ) == 0 ) { StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSQuery( top ); uassert(17131, maxTimeMS.getStatus().reason(), maxTimeMS.isOK()); _maxTimeMS = maxTimeMS.getValue(); } else if ( strcmp( "comment" , name ) == 0 ) { ; // no-op } } } if ( _snapshot ) { uassert( 12001 , "E12001 can't sort with $snapshot", _order.isEmpty() ); uassert( 12002 , "E12002 can't use hint with $snapshot", _hint.isEmpty() ); } }
Status ReplSetHeartbeatResponse::initialize(const BSONObj& doc) { const BSONElement electionTimeElement = doc[kElectionTimeFieldName]; if (electionTimeElement.eoo()) { _electionTimeSet = false; } else if (electionTimeElement.type() == Timestamp) { _electionTimeSet = true; _electionTime = electionTimeElement._opTime(); } else if (electionTimeElement.type() == Date) { _electionTime = true; _electionTime = OpTime(electionTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kElectionTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(electionTimeElement.type())); } const BSONElement timeElement = doc[kTimeFieldName]; if (timeElement.eoo()) { _timeSet = false; } else if (timeElement.isNumber()) { _timeSet = true; _time = Seconds(timeElement.numberLong()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kTimeFieldName << "\" field in reponse to replSetHeartbeat " "command to have a numeric type, but found type " << typeName(timeElement.type())); } const BSONElement opTimeElement = doc[kOpTimeFieldName]; if (opTimeElement.eoo()) { _opTimeSet = false; } else if (opTimeElement.type() == Timestamp) { _opTimeSet = true; _opTime = opTimeElement._opTime(); } else if (opTimeElement.type() == Date) { _opTimeSet = true; _opTime = OpTime(opTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kOpTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(opTimeElement.type())); } const BSONElement electableElement = doc[kIsElectableFieldName]; if (electableElement.eoo()) { _electableSet = false; } else { _electableSet = true; _electable = electableElement.trueValue(); } _mismatch = doc[kMismatchFieldName].trueValue(); _isReplSet = doc[kIsReplSetFieldName].trueValue(); const BSONElement memberStateElement = doc[kMemberStateFieldName]; if (memberStateElement.eoo()) { _stateSet = false; } else if (memberStateElement.type() != NumberInt && memberStateElement.type() != NumberLong) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kMemberStateFieldName << "\" field in response to replSetHeartbeat " " command to have type NumberInt or NumberLong, but found type " << typeName(memberStateElement.type())); } else { long long stateInt = memberStateElement.numberLong(); if (stateInt < 0 || stateInt > MemberState::RS_MAX) { return Status(ErrorCodes::BadValue, str::stream() << "Value for \"" << kMemberStateFieldName << "\" in response to replSetHeartbeat is " " out of range; legal values are non-negative and no more than " << MemberState::RS_MAX); } _state = MemberState(static_cast<int>(stateInt)); } _stateDisagreement = doc[kHasStateDisagreementFieldName].trueValue(); const BSONElement versionElement = doc[kConfigVersionFieldName]; if (versionElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << "Response to replSetHeartbeat missing required \"" << kConfigVersionFieldName << " field"); } if (versionElement.type() != NumberInt) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigVersionFieldName << "\" field in response to replSetHeartbeat to have " "type NumberInt, but found " << typeName(versionElement.type())); } _version = versionElement.numberInt(); const BSONElement replSetNameElement = doc[kReplSetFieldName]; if (replSetNameElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << "Response to replSetHeartbeat missing required \"" << kReplSetFieldName << "\" field"); } if (replSetNameElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kReplSetFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(replSetNameElement.type())); } _setName = replSetNameElement.String(); const BSONElement hbMsgElement = doc[kHbMessageFieldName]; if (hbMsgElement.eoo()) { _hbmsg.clear(); } else if (hbMsgElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kHbMessageFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(hbMsgElement.type())); } _hbmsg = hbMsgElement.String(); const BSONElement syncingToElement = doc[kSyncSourceFieldName]; if (syncingToElement.eoo()) { _syncingTo.clear(); } else if (syncingToElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kSyncSourceFieldName << "\" field in response to replSetHeartbeat to " "have type String, but found " << typeName(syncingToElement.type())); } _syncingTo = syncingToElement.String(); const BSONElement rsConfigElement = doc[kConfigFieldName]; if (rsConfigElement.eoo()) { _configSet = false; _config = ReplicaSetConfig(); } else if (rsConfigElement.type() != Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigFieldName << "\" in response to replSetHeartbeat to have type " "Object, but found " << typeName(rsConfigElement.type())); } _configSet = true; return _config.initialize(rsConfigElement.Obj()); }
ProjectionStage::ProjectionStage(const ProjectionStageParams& params, WorkingSet* ws, PlanStage* child) : _ws(ws), _child(child), _projImpl(params.projImpl) { if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { _exec.reset(new ProjectionExec(params.projObj, params.fullExpression)); } else { // We shouldn't need the full expression if we're fast-pathing. invariant(NULL == params.fullExpression); _projObj = params.projObj; // Sanity-check the input. invariant(_projObj.isOwned()); invariant(!_projObj.isEmpty()); // The _id is included by default. bool includeId = true; // Figure out what fields are in the projection. TODO: we can get this from the // ParsedProjection...modify that to have this type instead of a vector. BSONObjIterator projObjIt(_projObj); while (projObjIt.more()) { BSONElement elt = projObjIt.next(); // Must deal with the _id case separately as there is an implicit _id: 1 in the // projection. if (mongoutils::str::equals(elt.fieldName(), kIdField) && !elt.trueValue()) { includeId = false; continue; } _includedFields.insert(elt.fieldNameStringData()); } if (includeId) { _includedFields.insert(kIdField); } // If we're pulling data out of one index we can pre-compute the indices of the fields // in the key that we pull data from and avoid looking up the field name each time. if (ProjectionStageParams::COVERED_ONE_INDEX == params.projImpl) { // Sanity-check. _coveredKeyObj = params.coveredKeyObj; invariant(_coveredKeyObj.isOwned()); BSONObjIterator kpIt(_coveredKeyObj); while (kpIt.more()) { BSONElement elt = kpIt.next(); unordered_set<StringData, StringData::Hasher>::iterator fieldIt; fieldIt = _includedFields.find(elt.fieldNameStringData()); if (_includedFields.end() == fieldIt) { // Push an unused value on the back to keep _includeKey and _keyFieldNames // in sync. _keyFieldNames.push_back(StringData()); _includeKey.push_back(false); } else { // If we are including this key field store its field name. _keyFieldNames.push_back(*fieldIt); _includeKey.push_back(true); } } } else { invariant(ProjectionStageParams::SIMPLE_DOC == params.projImpl); } } }
Status WriteConcernOptions::parse(const BSONObj& obj) { reset(); if (obj.isEmpty()) { return Status(ErrorCodes::FailedToParse, "write concern object cannot be empty"); } BSONElement jEl; BSONElement fsyncEl; BSONElement wEl; for (auto e : obj) { const auto fieldName = e.fieldNameStringData(); if (fieldName == kJFieldName) { jEl = e; if (!jEl.isNumber() && jEl.type() != Bool) { return Status(ErrorCodes::FailedToParse, "j must be numeric or a boolean value"); } } else if (fieldName == kFSyncFieldName) { fsyncEl = e; if (!fsyncEl.isNumber() && fsyncEl.type() != Bool) { return Status(ErrorCodes::FailedToParse, "fsync must be numeric or a boolean value"); } } else if (fieldName == kWFieldName) { wEl = e; } else if (fieldName == kWTimeoutFieldName) { wTimeout = e.numberInt(); } else if (fieldName == kWElectionIdFieldName) { // Ignore. } else if (fieldName == kWOpTimeFieldName) { // Ignore. } else if (fieldName.equalCaseInsensitive(kGetLastErrorFieldName)) { // Ignore GLE field. } else { return Status(ErrorCodes::FailedToParse, str::stream() << "unrecognized write concern field: " << fieldName); } } const bool j = jEl.trueValue(); const bool fsync = fsyncEl.trueValue(); if (j && fsync) return Status(ErrorCodes::FailedToParse, "fsync and j options cannot be used together"); if (j) { syncMode = SyncMode::JOURNAL; } else if (fsync) { syncMode = SyncMode::FSYNC; } else if (!jEl.eoo()) { syncMode = SyncMode::NONE; } if (wEl.isNumber()) { wNumNodes = wEl.numberInt(); } else if (wEl.type() == String) { wMode = wEl.valuestrsafe(); } else if (wEl.eoo() || wEl.type() == jstNULL || wEl.type() == Undefined) { wNumNodes = 1; } else { return Status(ErrorCodes::FailedToParse, "w has to be a number or a string"); } return Status::OK(); }
void Projection::init( const BSONObj& o ) { massert( 10371 , "can only add to Projection once", _source.isEmpty()); _source = o; BSONObjIterator i( o ); int true_false = -1; while ( i.more() ) { BSONElement e = i.next(); if ( ! e.isNumber() ) _hasNonSimple = true; if (e.type() == Object) { BSONObj obj = e.embeddedObject(); BSONElement e2 = obj.firstElement(); if ( strcmp(e2.fieldName(), "$slice") == 0 ) { if (e2.isNumber()) { int i = e2.numberInt(); if (i < 0) add(e.fieldName(), i, -i); // limit is now positive else add(e.fieldName(), 0, i); } else if (e2.type() == Array) { BSONObj arr = e2.embeddedObject(); uassert(13099, "$slice array wrong size", arr.nFields() == 2 ); BSONObjIterator it(arr); int skip = it.next().numberInt(); int limit = it.next().numberInt(); uassert(13100, "$slice limit must be positive", limit > 0 ); add(e.fieldName(), skip, limit); } else { uassert(13098, "$slice only supports numbers and [skip, limit] arrays", false); } } else { uassert(13097, string("Unsupported projection option: ") + obj.firstElementFieldName(), false); } } else if (!strcmp(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; } else { add (e.fieldName(), e.trueValue()); // validate input if (true_false == -1) { true_false = e.trueValue(); _include = !e.trueValue(); } else { uassert( 10053 , "You cannot currently mix including and excluding fields. Contact us if this is an issue." , (bool)true_false == e.trueValue() ); } } } }
Status ReplSetHeartbeatResponse::initialize(const BSONObj& doc, long long term) { // Old versions set this even though they returned not "ok" _mismatch = doc[kMismatchFieldName].trueValue(); if (_mismatch) return Status(ErrorCodes::InconsistentReplicaSetNames, "replica set name doesn't match."); // Old versions sometimes set the replica set name ("set") but ok:0 const BSONElement replSetNameElement = doc[kReplSetFieldName]; if (replSetNameElement.eoo()) { _setName.clear(); } else if (replSetNameElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kReplSetFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(replSetNameElement.type())); } else { _setName = replSetNameElement.String(); } if (_setName.empty() && !doc[kOkFieldName].trueValue()) { std::string errMsg = doc[kErrMsgFieldName].str(); BSONElement errCodeElem = doc[kErrorCodeFieldName]; if (errCodeElem.ok()) { if (!errCodeElem.isNumber()) return Status(ErrorCodes::BadValue, "Error code is not a number!"); int errorCode = errCodeElem.numberInt(); return Status(ErrorCodes::Error(errorCode), errMsg); } return Status(ErrorCodes::UnknownError, errMsg); } const BSONElement hasDataElement = doc[kHasDataFieldName]; _hasDataSet = !hasDataElement.eoo(); _hasData = hasDataElement.trueValue(); const BSONElement electionTimeElement = doc[kElectionTimeFieldName]; if (electionTimeElement.eoo()) { _electionTimeSet = false; } else if (electionTimeElement.type() == bsonTimestamp) { _electionTimeSet = true; _electionTime = electionTimeElement.timestamp(); } else if (electionTimeElement.type() == Date) { _electionTimeSet = true; _electionTime = Timestamp(electionTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kElectionTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(electionTimeElement.type())); } const BSONElement timeElement = doc[kTimeFieldName]; if (timeElement.eoo()) { _timeSet = false; } else if (timeElement.isNumber()) { _timeSet = true; _time = Seconds(timeElement.numberLong()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kTimeFieldName << "\" field in response to replSetHeartbeat " "command to have a numeric type, but found type " << typeName(timeElement.type())); } _isReplSet = doc[kIsReplSetFieldName].trueValue(); Status termStatus = bsonExtractIntegerField(doc, kTermFieldName, &_term); if (!termStatus.isOK() && termStatus != ErrorCodes::NoSuchKey) { return termStatus; } // In order to support both the 3.0(V0) and 3.2(V1) heartbeats we must parse the OpTime // field based on its type. If it is a Date, we parse it as the timestamp and use // initialize's term argument to complete the OpTime type. If it is an Object, then it's // V1 and we construct an OpTime out of its nested fields. const BSONElement opTimeElement = doc[kOpTimeFieldName]; if (opTimeElement.eoo()) { _opTimeSet = false; } else if (opTimeElement.type() == bsonTimestamp) { _opTimeSet = true; _opTime = OpTime(opTimeElement.timestamp(), term); } else if (opTimeElement.type() == Date) { _opTimeSet = true; _opTime = OpTime(Timestamp(opTimeElement.date()), term); } else if (opTimeElement.type() == Object) { Status status = bsonExtractOpTimeField(doc, kOpTimeFieldName, &_opTime); _opTimeSet = true; // since a v1 OpTime was in the response, the member must be part of a replset _isReplSet = true; } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kOpTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(opTimeElement.type())); } const BSONElement electableElement = doc[kIsElectableFieldName]; if (electableElement.eoo()) { _electableSet = false; } else { _electableSet = true; _electable = electableElement.trueValue(); } const BSONElement memberStateElement = doc[kMemberStateFieldName]; if (memberStateElement.eoo()) { _stateSet = false; } else if (memberStateElement.type() != NumberInt && memberStateElement.type() != NumberLong) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kMemberStateFieldName << "\" field in response to replSetHeartbeat " "command to have type NumberInt or NumberLong, but found type " << typeName(memberStateElement.type())); } else { long long stateInt = memberStateElement.numberLong(); if (stateInt < 0 || stateInt > MemberState::RS_MAX) { return Status(ErrorCodes::BadValue, str::stream() << "Value for \"" << kMemberStateFieldName << "\" in response to replSetHeartbeat is " "out of range; legal values are non-negative and no more than " << MemberState::RS_MAX); } _stateSet = true; _state = MemberState(static_cast<int>(stateInt)); } _stateDisagreement = doc[kHasStateDisagreementFieldName].trueValue(); // Not required for the case of uninitialized members -- they have no config const BSONElement configVersionElement = doc[kConfigVersionFieldName]; // If we have an optime then we must have a configVersion if (_opTimeSet && configVersionElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << "Response to replSetHeartbeat missing required \"" << kConfigVersionFieldName << "\" field even though initialized"); } // If there is a "v" (config version) then it must be an int. if (!configVersionElement.eoo() && configVersionElement.type() != NumberInt) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigVersionFieldName << "\" field in response to replSetHeartbeat to have " "type NumberInt, but found " << typeName(configVersionElement.type())); } _configVersion = configVersionElement.numberInt(); const BSONElement hbMsgElement = doc[kHbMessageFieldName]; if (hbMsgElement.eoo()) { _hbmsg.clear(); } else if (hbMsgElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kHbMessageFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(hbMsgElement.type())); } else { _hbmsg = hbMsgElement.String(); } const BSONElement syncingToElement = doc[kSyncSourceFieldName]; if (syncingToElement.eoo()) { _syncingTo = HostAndPort(); } else if (syncingToElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kSyncSourceFieldName << "\" field in response to replSetHeartbeat to " "have type String, but found " << typeName(syncingToElement.type())); } else { _syncingTo = HostAndPort(syncingToElement.String()); } const BSONElement rsConfigElement = doc[kConfigFieldName]; if (rsConfigElement.eoo()) { _configSet = false; _config = ReplicaSetConfig(); return Status::OK(); } else if (rsConfigElement.type() != Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigFieldName << "\" in response to replSetHeartbeat to have type " "Object, but found " << typeName(rsConfigElement.type())); } _configSet = true; return _config.initialize(rsConfigElement.Obj()); }
bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result) { _runCalled = true; long long start = Listener::getElapsedTimeMillis(); BSONObjBuilder timeBuilder(256); const auto authSession = AuthorizationSession::get(ClientBasic::getCurrent()); // --- basic fields that are global result.append("host", prettyHostName()); result.append("version", versionString); result.append("process", serverGlobalParams.binaryName); result.append("pid", ProcessId::getCurrent().asLongLong()); result.append("uptime", (double)(time(0) - serverGlobalParams.started)); result.append("uptimeMillis", (long long)(curTimeMillis64() - _started)); result.append("uptimeEstimate", (double)(start / 1000)); result.appendDate("localTime", jsTime()); timeBuilder.appendNumber("after basic", Listener::getElapsedTimeMillis() - start); // --- all sections for (SectionMap::const_iterator i = _sections->begin(); i != _sections->end(); ++i) { ServerStatusSection* section = i->second; std::vector<Privilege> requiredPrivileges; section->addRequiredPrivileges(&requiredPrivileges); if (!authSession->isAuthorizedForPrivileges(requiredPrivileges)) continue; bool include = section->includeByDefault(); BSONElement e = cmdObj[section->getSectionName()]; if (e.type()) { include = e.trueValue(); } if (!include) continue; BSONObj data = section->generateSection(txn, e); if (data.isEmpty()) continue; result.append(section->getSectionName(), data); timeBuilder.appendNumber( static_cast<string>(str::stream() << "after " << section->getSectionName()), Listener::getElapsedTimeMillis() - start); } // --- counters bool includeMetricTree = MetricTree::theMetricTree != NULL; if (cmdObj["metrics"].type() && !cmdObj["metrics"].trueValue()) includeMetricTree = false; if (includeMetricTree) { MetricTree::theMetricTree->appendTo(result); } // --- some hard coded global things hard to pull out { RamLog::LineIterator rl(RamLog::get("warnings")); if (rl.lastWrite() >= time(0) - (10 * 60)) { // only show warnings from last 10 minutes BSONArrayBuilder arr(result.subarrayStart("warnings")); while (rl.more()) { arr.append(rl.next()); } arr.done(); } } timeBuilder.appendNumber("at end", Listener::getElapsedTimeMillis() - start); if (Listener::getElapsedTimeMillis() - start > 1000) { BSONObj t = timeBuilder.obj(); log() << "serverStatus was very slow: " << t << endl; result.append("timing", t); } return true; }
void Projection::init( const BSONObj& o ) { massert( 10371 , "can only add to Projection once", _source.isEmpty()); _source = o; BSONObjIterator i( o ); int true_false = -1; while ( i.more() ) { BSONElement e = i.next(); if ( ! e.isNumber() ) _hasNonSimple = true; if (e.type() == Object) { BSONObj obj = e.embeddedObject(); BSONElement e2 = obj.firstElement(); if ( mongoutils::str::equals( e2.fieldName(), "$slice" ) ) { if (e2.isNumber()) { int i = e2.numberInt(); if (i < 0) add(e.fieldName(), i, -i); // limit is now positive else add(e.fieldName(), 0, i); } else if (e2.type() == Array) { BSONObj arr = e2.embeddedObject(); uassert(13099, "$slice array wrong size", arr.nFields() == 2 ); BSONObjIterator it(arr); int skip = it.next().numberInt(); int limit = it.next().numberInt(); uassert(13100, "$slice limit must be positive", limit > 0 ); add(e.fieldName(), skip, limit); } else { uassert(13098, "$slice only supports numbers and [skip, limit] arrays", false); } } else if ( mongoutils::str::equals( e2.fieldName(), "$elemMatch" ) ) { // validate $elemMatch arguments and dependencies uassert( 16342, "elemMatch: invalid argument. object required.", e2.type() == Object ); uassert( 16343, "Cannot specify positional operator and $elemMatch" " (currently unsupported).", _arrayOpType != ARRAY_OP_POSITIONAL ); uassert( 16344, "Cannot use $elemMatch projection on a nested field" " (currently unsupported).", ! mongoutils::str::contains( e.fieldName(), '.' ) ); _arrayOpType = ARRAY_OP_ELEM_MATCH; // initialize new Matcher object(s) _matchers.insert( make_pair( mongoutils::str::before( e.fieldName(), '.' ), boost::make_shared<Matcher>( e.wrap(), true ) ) ); add( e.fieldName(), true ); } else { uasserted(13097, string("Unsupported projection option: ") + obj.firstElementFieldName() ); } } else if (!strcmp(e.fieldName(), "_id") && !e.trueValue()) { _includeID = false; } else { add( e.fieldName(), e.trueValue() ); // validate input if (true_false == -1) { true_false = e.trueValue(); _include = !e.trueValue(); } else { uassert( 10053 , "You cannot currently mix including and excluding fields. " "Contact us if this is an issue." , (bool)true_false == e.trueValue() ); } } if ( mongoutils::str::contains( e.fieldName(), ".$" ) ) { // positional op found; verify dependencies uassert( 16345, "Cannot exclude array elements with the positional operator" " (currently unsupported).", e.trueValue() ); uassert( 16346, "Cannot specify more than one positional array element per query" " (currently unsupported).", _arrayOpType != ARRAY_OP_POSITIONAL ); uassert( 16347, "Cannot specify positional operator and $elemMatch" " (currently unsupported).", _arrayOpType != ARRAY_OP_ELEM_MATCH ); _arrayOpType = ARRAY_OP_POSITIONAL; } } }
Config::Config( const string& _dbname , const BSONObj& cmdObj ) { dbname = _dbname; ns = dbname + "." + cmdObj.firstElement().valuestr(); verbose = cmdObj["verbose"].trueValue(); uassert( 13602 , "outType is no longer a valid option" , cmdObj["outType"].eoo() ); if ( cmdObj["out"].type() == String ) { finalShort = cmdObj["out"].String(); outType = REPLACE; } else if ( cmdObj["out"].type() == Object ) { BSONObj o = cmdObj["out"].embeddedObject(); BSONElement e = o.firstElement(); string t = e.fieldName(); if ( t == "normal" || t == "replace" ) { outType = REPLACE; finalShort = e.String(); } else if ( t == "merge" ) { outType = MERGE; finalShort = e.String(); } else if ( t == "reduce" ) { outType = REDUCE; finalShort = e.String(); } else if ( t == "inline" ) { outType = INMEMORY; } else { uasserted( 13522 , str::stream() << "unknown out specifier [" << t << "]" ); } if (o.hasElement("db")) { outDB = o["db"].String(); } } else { uasserted( 13606 , "'out' has to be a string or an object" ); } if ( outType != INMEMORY ) { // setup names tempLong = str::stream() << (outDB.empty() ? dbname : outDB) << ".tmp.mr." << cmdObj.firstElement().String() << "_" << finalShort << "_" << JOB_NUMBER++; incLong = tempLong + "_inc"; finalLong = str::stream() << (outDB.empty() ? dbname : outDB) << "." << finalShort; } { // scope and code if ( cmdObj["scope"].type() == Object ) scopeSetup = cmdObj["scope"].embeddedObjectUserCheck(); mapper.reset( new JSMapper( cmdObj["map"] ) ); reducer.reset( new JSReducer( cmdObj["reduce"] ) ); if ( cmdObj["finalize"].type() && cmdObj["finalize"].trueValue() ) finalizer.reset( new JSFinalizer( cmdObj["finalize"] ) ); if ( cmdObj["mapparams"].type() == Array ) { mapParams = cmdObj["mapparams"].embeddedObjectUserCheck(); } } { // query options BSONElement q = cmdObj["query"]; if ( q.type() == Object ) filter = q.embeddedObjectUserCheck(); else uassert( 13608 , "query has to be blank or an Object" , ! q.trueValue() ); BSONElement s = cmdObj["sort"]; if ( s.type() == Object ) sort = s.embeddedObjectUserCheck(); else uassert( 13609 , "sort has to be blank or an Object" , ! s.trueValue() ); if ( cmdObj["limit"].isNumber() ) limit = cmdObj["limit"].numberLong(); else limit = 0; } }
Status ReplSetHeartbeatResponse::initialize(const BSONObj& doc) { // Old versions set this even though they returned not "ok" _mismatch = doc[kMismatchFieldName].trueValue(); if (_mismatch) return Status(ErrorCodes::InconsistentReplicaSetNames, "replica set name doesn't match."); // Old versions sometimes set the replica set name ("set") but ok:0 const BSONElement replSetNameElement = doc[kReplSetFieldName]; if (replSetNameElement.eoo()) { _setName.clear(); } else if (replSetNameElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kReplSetFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(replSetNameElement.type())); } else { _setName = replSetNameElement.String(); } if (_setName.empty() && !doc[kOkFieldName].trueValue()) { std::string errMsg = doc[kErrMsgFieldName].str(); BSONElement errCodeElem = doc[kErrorCodeFieldName]; if (errCodeElem.ok()) { if (!errCodeElem.isNumber()) return Status(ErrorCodes::BadValue, "Error code is not a number!"); int errorCode = errCodeElem.numberInt(); return Status(ErrorCodes::Error(errorCode), errMsg); } return Status(ErrorCodes::UnknownError, errMsg); } const BSONElement hasDataElement = doc[kHasDataFieldName]; _hasDataSet = !hasDataElement.eoo(); _hasData = hasDataElement.trueValue(); const BSONElement electionTimeElement = doc[kElectionTimeFieldName]; if (electionTimeElement.eoo()) { _electionTimeSet = false; } else if (electionTimeElement.type() == Timestamp) { _electionTimeSet = true; _electionTime = electionTimeElement._opTime(); } else if (electionTimeElement.type() == Date) { _electionTimeSet = true; _electionTime = OpTime(electionTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kElectionTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(electionTimeElement.type())); } const BSONElement timeElement = doc[kTimeFieldName]; if (timeElement.eoo()) { _timeSet = false; } else if (timeElement.isNumber()) { _timeSet = true; _time = Seconds(timeElement.numberLong()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kTimeFieldName << "\" field in response to replSetHeartbeat " "command to have a numeric type, but found type " << typeName(timeElement.type())); } const BSONElement opTimeElement = doc[kOpTimeFieldName]; if (opTimeElement.eoo()) { _opTimeSet = false; } else if (opTimeElement.type() == Timestamp) { _opTimeSet = true; _opTime = opTimeElement._opTime(); } else if (opTimeElement.type() == Date) { _opTimeSet = true; _opTime = OpTime(opTimeElement.date()); } else { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kOpTimeFieldName << "\" field in response to replSetHeartbeat " "command to have type Date or Timestamp, but found type " << typeName(opTimeElement.type())); } const BSONElement electableElement = doc[kIsElectableFieldName]; if (electableElement.eoo()) { _electableSet = false; } else { _electableSet = true; _electable = electableElement.trueValue(); } _isReplSet = doc[kIsReplSetFieldName].trueValue(); const BSONElement memberStateElement = doc[kMemberStateFieldName]; if (memberStateElement.eoo()) { _stateSet = false; } else if (memberStateElement.type() != NumberInt && memberStateElement.type() != NumberLong) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kMemberStateFieldName << "\" field in response to replSetHeartbeat " "command to have type NumberInt or NumberLong, but found type " << typeName(memberStateElement.type())); } else { long long stateInt = memberStateElement.numberLong(); if (stateInt < 0 || stateInt > MemberState::RS_MAX) { return Status(ErrorCodes::BadValue, str::stream() << "Value for \"" << kMemberStateFieldName << "\" in response to replSetHeartbeat is " "out of range; legal values are non-negative and no more than " << MemberState::RS_MAX); } _stateSet = true; _state = MemberState(static_cast<int>(stateInt)); } _stateDisagreement = doc[kHasStateDisagreementFieldName].trueValue(); // Not required for the case of uninitialized members -- they have no config const BSONElement versionElement = doc[kConfigVersionFieldName]; // If we have an optime then we must have a version if (_opTimeSet && versionElement.eoo()) { return Status(ErrorCodes::NoSuchKey, str::stream() << "Response to replSetHeartbeat missing required \"" << kConfigVersionFieldName << "\" field even though initialized"); } // If there is a "v" (config version) then it must be an int. if (!versionElement.eoo() && versionElement.type() != NumberInt) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigVersionFieldName << "\" field in response to replSetHeartbeat to have " "type NumberInt, but found " << typeName(versionElement.type())); } _version = versionElement.numberInt(); const BSONElement hbMsgElement = doc[kHbMessageFieldName]; if (hbMsgElement.eoo()) { _hbmsg.clear(); } else if (hbMsgElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kHbMessageFieldName << "\" field in response to replSetHeartbeat to have " "type String, but found " << typeName(hbMsgElement.type())); } else { _hbmsg = hbMsgElement.String(); } const BSONElement syncingToElement = doc[kSyncSourceFieldName]; if (syncingToElement.eoo()) { _syncingTo.clear(); } else if (syncingToElement.type() != String) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kSyncSourceFieldName << "\" field in response to replSetHeartbeat to " "have type String, but found " << typeName(syncingToElement.type())); } else { _syncingTo = syncingToElement.String(); } const BSONElement rsConfigElement = doc[kConfigFieldName]; if (rsConfigElement.eoo()) { _configSet = false; _config = ReplicaSetConfig(); return Status::OK(); } else if (rsConfigElement.type() != Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigFieldName << "\" in response to replSetHeartbeat to have type " "Object, but found " << typeName(rsConfigElement.type())); } _configSet = true; return _config.initialize(rsConfigElement.Obj()); }
Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs) { unordered_set<std::string> validFieldNames; validFieldNames.insert("rolesInfo"); validFieldNames.insert("showPrivileges"); validFieldNames.insert("showAuthenticationRestrictions"); validFieldNames.insert("showBuiltinRoles"); Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames); if (!status.isOK()) { return status; } if (cmdObj["rolesInfo"].numberInt() == 1) { parsedArgs->allForDB = true; } else if (cmdObj["rolesInfo"].type() == Array) { status = parseRoleNamesFromBSONArray( BSONArray(cmdObj["rolesInfo"].Obj()), dbname, &parsedArgs->roleNames); if (!status.isOK()) { return status; } } else { RoleName name; status = _parseNameFromBSONElement(cmdObj["rolesInfo"], dbname, AuthorizationManager::ROLE_NAME_FIELD_NAME, AuthorizationManager::ROLE_DB_FIELD_NAME, &name); if (!status.isOK()) { return status; } parsedArgs->roleNames.push_back(name); } BSONElement showPrivileges = cmdObj["showPrivileges"]; if (showPrivileges.eoo()) { parsedArgs->privilegeFormat = PrivilegeFormat::kOmit; } else if (showPrivileges.isNumber() || showPrivileges.isBoolean()) { parsedArgs->privilegeFormat = showPrivileges.trueValue() ? PrivilegeFormat::kShowSeparate : PrivilegeFormat::kOmit; } else if (showPrivileges.type() == BSONType::String && showPrivileges.String() == "asUserFragment") { parsedArgs->privilegeFormat = PrivilegeFormat::kShowAsUserFragment; } else { return Status(ErrorCodes::FailedToParse, str::stream() << "Failed to parse 'showPrivileges'. 'showPrivileges' should " "either be a boolean or the string 'asUserFragment', given: " << showPrivileges.toString()); } const auto showAuthenticationRestrictions = cmdObj["showAuthenticationRestrictions"]; if (showAuthenticationRestrictions.eoo()) { parsedArgs->authenticationRestrictionsFormat = AuthenticationRestrictionsFormat::kOmit; } else if (parsedArgs->privilegeFormat == PrivilegeFormat::kShowAsUserFragment) { return Status( ErrorCodes::UnsupportedFormat, "showAuthenticationRestrictions may not be used with showPrivileges='asUserFragment'"); } else { bool show; status = bsonExtractBooleanField(cmdObj, "showAuthenticationRestrictions", &show); if (!status.isOK()) { return status; } parsedArgs->authenticationRestrictionsFormat = show ? AuthenticationRestrictionsFormat::kShow : AuthenticationRestrictionsFormat::kOmit; } status = bsonExtractBooleanFieldWithDefault( cmdObj, "showBuiltinRoles", false, &parsedArgs->showBuiltinRoles); if (!status.isOK()) { return status; } return Status::OK(); }
bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { _runCalled = true; long long start = Listener::getElapsedTimeMillis(); BSONObjBuilder timeBuilder(256); const ClientBasic* myClientBasic = ClientBasic::getCurrent(); AuthorizationManager* authManager = myClientBasic->getAuthorizationManager(); // --- basic fields that are global result.append("host", prettyHostName() ); result.append("version", versionString); result.append("process",cmdLine.binaryName); result.append("pid", (int)getpid()); result.append("uptime",(double) (time(0)-cmdLine.started)); result.append("uptimeMillis", (long long)(curTimeMillis64()-_started)); result.append("uptimeEstimate",(double) (start/1000)); result.appendDate( "localTime" , jsTime() ); timeBuilder.appendNumber( "after basic" , Listener::getElapsedTimeMillis() - start ); // --- all sections for ( SectionMap::const_iterator i = _sections->begin(); i != _sections->end(); ++i ) { ServerStatusSection* section = i->second; std::vector<Privilege> requiredPrivileges; section->addRequiredPrivileges(&requiredPrivileges); if (!authManager->checkAuthForPrivileges(requiredPrivileges).isOK()) continue; bool include = section->includeByDefault(); BSONElement e = cmdObj[section->getSectionName()]; if ( e.type() ) { include = e.trueValue(); } if ( ! include ) continue; BSONObj data = section->generateSection(e); if ( data.isEmpty() ) continue; result.append( section->getSectionName(), data ); timeBuilder.appendNumber( static_cast<string>(str::stream() << "after " << section->getSectionName()), Listener::getElapsedTimeMillis() - start ); } // --- counters if ( MetricTree::theMetricTree ) { MetricTree::theMetricTree->appendTo( result ); } // --- some hard coded global things hard to pull out { RamLog* rl = RamLog::get( "warnings" ); massert(15880, "no ram log for warnings?" , rl); if (rl->lastWrite() >= time(0)-(10*60)){ // only show warnings from last 10 minutes vector<const char*> lines; rl->get( lines ); BSONArrayBuilder arr( result.subarrayStart( "warnings" ) ); for ( unsigned i=std::max(0,(int)lines.size()-10); i<lines.size(); i++ ) arr.append( lines[i] ); arr.done(); } } timeBuilder.appendNumber( "at end" , Listener::getElapsedTimeMillis() - start ); if ( Listener::getElapsedTimeMillis() - start > 1000 ) { BSONObj t = timeBuilder.obj(); log() << "serverStatus was very slow: " << t << endl; result.append( "timing" , t ); } return true; }
bool Command::isHelpRequest(const BSONElement& helpElem) { return !helpElem.eoo() && helpElem.trueValue(); }
void ParallelSortClusteredCursor::_finishCons() { _numServers = _servers.size(); _lastFrom = 0; _cursors = 0; if (!_qSpec.isEmpty()) { _needToSkip = _qSpec.ntoskip(); _cursors = 0; _sortKey = _qSpec.sort(); _fields = _qSpec.fields(); } // Partition sort key fields into (a) text meta fields and (b) all other fields. set<string> textMetaSortKeyFields; set<string> normalSortKeyFields; // Transform _sortKey fields {a:{$meta:"textScore"}} into {a:-1}, in order to apply the // merge sort for text metadata in the correct direction. BSONObjBuilder transformedSortKeyBuilder; BSONObjIterator sortKeyIt(_sortKey); while (sortKeyIt.more()) { BSONElement e = sortKeyIt.next(); if (LiteParsedQuery::isTextScoreMeta(e)) { textMetaSortKeyFields.insert(e.fieldName()); transformedSortKeyBuilder.append(e.fieldName(), -1); } else { normalSortKeyFields.insert(e.fieldName()); transformedSortKeyBuilder.append(e); } } _sortKey = transformedSortKeyBuilder.obj(); // Verify that that all text metadata sort fields are in the projection. For all other sort // fields, copy them into the projection if they are missing (and if projection is // negative). if (!_sortKey.isEmpty() && !_fields.isEmpty()) { BSONObjBuilder b; bool isNegative = false; { BSONObjIterator i(_fields); while (i.more()) { BSONElement e = i.next(); b.append(e); string fieldName = e.fieldName(); if (LiteParsedQuery::isTextScoreMeta(e)) { textMetaSortKeyFields.erase(fieldName); } else { // exact field bool found = normalSortKeyFields.erase(fieldName); // subfields set<string>::const_iterator begin = normalSortKeyFields.lower_bound(fieldName + ".\x00"); set<string>::const_iterator end = normalSortKeyFields.lower_bound(fieldName + ".\xFF"); normalSortKeyFields.erase(begin, end); if (!e.trueValue()) { uassert(13431, "have to have sort key in projection and removing it", !found && begin == end); } else if (!e.isABSONObj()) { isNegative = true; } } } } if (isNegative) { for (set<string>::const_iterator it(normalSortKeyFields.begin()), end(normalSortKeyFields.end()); it != end; ++it) { b.append(*it, 1); } } _fields = b.obj(); } if (!_qSpec.isEmpty()) { _qSpec.setFields(_fields); } uassert( 17306, "have to have all text meta sort keys in projection", textMetaSortKeyFields.empty()); }
// static Status ParsedProjection::make(const BSONObj& spec, const MatchExpression* const query, ParsedProjection** out, const MatchExpressionParser::WhereCallback& whereCallback) { // Are we including or excluding fields? Values: // -1 when we haven't initialized it. // 1 when we're including // 0 when we're excluding. int include_exclude = -1; // If any of these are 'true' the projection isn't covered. bool include = true; bool hasNonSimple = false; bool hasDottedField = false; bool includeID = true; bool hasIndexKeyProjection = false; bool wantGeoNearPoint = false; bool wantGeoNearDistance = false; // Until we see a positional or elemMatch operator we're normal. ArrayOpType arrayOpType = ARRAY_OP_NORMAL; BSONObjIterator it(spec); while (it.more()) { BSONElement e = it.next(); if (!e.isNumber() && !e.isBoolean()) { hasNonSimple = true; } if (Object == e.type()) { BSONObj obj = e.embeddedObject(); if (1 != obj.nFields()) { return Status(ErrorCodes::BadValue, ">1 field in obj: " + obj.toString()); } BSONElement e2 = obj.firstElement(); if (mongoutils::str::equals(e2.fieldName(), "$slice")) { if (e2.isNumber()) { // This is A-OK. } else if (e2.type() == Array) { BSONObj arr = e2.embeddedObject(); if (2 != arr.nFields()) { return Status(ErrorCodes::BadValue, "$slice array wrong size"); } BSONObjIterator it(arr); // Skip over 'skip'. it.next(); int limit = it.next().numberInt(); if (limit <= 0) { return Status(ErrorCodes::BadValue, "$slice limit must be positive"); } } else { return Status(ErrorCodes::BadValue, "$slice only supports numbers and [skip, limit] arrays"); } } else if (mongoutils::str::equals(e2.fieldName(), "$elemMatch")) { // Validate $elemMatch arguments and dependencies. if (Object != e2.type()) { return Status(ErrorCodes::BadValue, "elemMatch: Invalid argument, object required."); } if (ARRAY_OP_POSITIONAL == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify positional operator and $elemMatch."); } if (mongoutils::str::contains(e.fieldName(), '.')) { return Status(ErrorCodes::BadValue, "Cannot use $elemMatch projection on a nested field."); } arrayOpType = ARRAY_OP_ELEM_MATCH; // Create a MatchExpression for the elemMatch. BSONObj elemMatchObj = e.wrap(); verify(elemMatchObj.isOwned()); // TODO: Is there a faster way of validating the elemMatchObj? StatusWithMatchExpression swme = MatchExpressionParser::parse(elemMatchObj, whereCallback); if (!swme.isOK()) { return swme.getStatus(); } delete swme.getValue(); } else if (mongoutils::str::equals(e2.fieldName(), "$meta")) { // Field for meta must be top level. We can relax this at some point. if (mongoutils::str::contains(e.fieldName(), '.')) { return Status(ErrorCodes::BadValue, "field for $meta cannot be nested"); } // Make sure the argument to $meta is something we recognize. // e.g. {x: {$meta: "textScore"}} if (String != e2.type()) { return Status(ErrorCodes::BadValue, "unexpected argument to $meta in proj"); } if (e2.valuestr() != LiteParsedQuery::metaTextScore && e2.valuestr() != LiteParsedQuery::metaRecordId && e2.valuestr() != LiteParsedQuery::metaIndexKey && e2.valuestr() != LiteParsedQuery::metaGeoNearDistance && e2.valuestr() != LiteParsedQuery::metaGeoNearPoint) { return Status(ErrorCodes::BadValue, "unsupported $meta operator: " + e2.str()); } // This clobbers everything else. if (e2.valuestr() == LiteParsedQuery::metaIndexKey) { hasIndexKeyProjection = true; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearDistance) { wantGeoNearDistance = true; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearPoint) { wantGeoNearPoint = true; } } else { return Status(ErrorCodes::BadValue, string("Unsupported projection option: ") + e.toString()); } } else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) { includeID = false; } else { // Projections of dotted fields aren't covered. if (mongoutils::str::contains(e.fieldName(), '.')) { hasDottedField = true; } // Validate input. if (include_exclude == -1) { // If we haven't specified an include/exclude, initialize include_exclude. // We expect further include/excludes to match it. include_exclude = e.trueValue(); include = !e.trueValue(); } else if (static_cast<bool>(include_exclude) != e.trueValue()) { // Make sure that the incl./excl. matches the previous. return Status(ErrorCodes::BadValue, "Projection cannot have a mix of inclusion and exclusion."); } } if (_isPositionalOperator(e.fieldName())) { // Validate the positional op. if (!e.trueValue()) { return Status(ErrorCodes::BadValue, "Cannot exclude array elements with the positional operator."); } if (ARRAY_OP_POSITIONAL == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify more than one positional proj. per query."); } if (ARRAY_OP_ELEM_MATCH == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify positional operator and $elemMatch."); } std::string after = mongoutils::str::after(e.fieldName(), ".$"); if (mongoutils::str::contains(after, ".$")) { mongoutils::str::stream ss; ss << "Positional projection '" << e.fieldName() << "' contains " << "the positional operator more than once."; return Status(ErrorCodes::BadValue, ss); } std::string matchfield = mongoutils::str::before(e.fieldName(), '.'); if (!_hasPositionalOperatorMatch(query, matchfield)) { mongoutils::str::stream ss; ss << "Positional projection '" << e.fieldName() << "' does not " << "match the query document."; return Status(ErrorCodes::BadValue, ss); } arrayOpType = ARRAY_OP_POSITIONAL; } } // Fill out the returned obj. unique_ptr<ParsedProjection> pp(new ParsedProjection()); // The positional operator uses the MatchDetails from the query // expression to know which array element was matched. pp->_requiresMatchDetails = arrayOpType == ARRAY_OP_POSITIONAL; // Save the raw spec. It should be owned by the LiteParsedQuery. verify(spec.isOwned()); pp->_source = spec; pp->_returnKey = hasIndexKeyProjection; // Dotted fields aren't covered, non-simple require match details, and as for include, "if // we default to including then we can't use an index because we don't know what we're // missing." pp->_requiresDocument = include || hasNonSimple || hasDottedField; // Add geoNear projections. pp->_wantGeoNearPoint = wantGeoNearPoint; pp->_wantGeoNearDistance = wantGeoNearDistance; // If it's possible to compute the projection in a covered fashion, populate _requiredFields // so the planner can perform projection analysis. if (!pp->_requiresDocument) { if (includeID) { pp->_requiredFields.push_back("_id"); } // The only way we could be here is if spec is only simple non-dotted-field projections. // Therefore we can iterate over spec to get the fields required. BSONObjIterator srcIt(spec); while (srcIt.more()) { BSONElement elt = srcIt.next(); // We've already handled the _id field before entering this loop. if (includeID && mongoutils::str::equals(elt.fieldName(), "_id")) { continue; } if (elt.trueValue()) { pp->_requiredFields.push_back(elt.fieldName()); } } } // returnKey clobbers everything. if (hasIndexKeyProjection) { pp->_requiresDocument = false; } *out = pp.release(); return Status::OK(); }
Status ReplSetHeartbeatResponseV1::initialize(const BSONObj& doc) { Status status = bsonCheckOnlyHasFields("ReplSetHeartbeatResponse", doc, kLegalHeartbeatFieldNames); if (!status.isOK()) return status; status = bsonExtractBooleanField(doc, kIsReplSetFieldName, &_isReplSet); if (!status.isOK()) return status; status = bsonExtractStringField(doc, kReplSetFieldName, &_setName); if (!status.isOK()) return status; long long stateInt; status = bsonExtractIntegerField(doc, kMemberStateFieldName, &stateInt); if (!status.isOK()) return status; if (stateInt < 0 || stateInt > MemberState::RS_MAX) { return Status(ErrorCodes::BadValue, str::stream() << "Value for \"" << kMemberStateFieldName << "\" in response to replSetHeartbeat is " "out of range; legal values are non-negative and no more than " << MemberState::RS_MAX); } _state = MemberState(static_cast<int>(stateInt)); // extracting the lastCommittedOp is a bit of a process BSONObj lastOpTime = doc[kLastOpTimeFieldName].Obj(); Timestamp ts; status = bsonExtractTimestampField(lastOpTime, kOpTimeFieldName, &ts); if (!status.isOK()) return status; long long term; status = bsonExtractIntegerField(lastOpTime, kTermFieldName, &term); if (!status.isOK()) return status; _lastOpTime = OpTime(lastOpTime[kOpTimeFieldName].timestamp(), lastOpTime[kTermFieldName].Long()); status = bsonExtractStringField(doc, kSyncSourceFieldName, &_syncingTo); if (!status.isOK()) return status; status = bsonExtractIntegerField(doc, kConfigVersionFieldName, &_configVersion); if (!status.isOK()) return status; status = bsonExtractIntegerField(doc, kPrimaryIdFieldName, &_primaryId); if (!status.isOK()) return status; status = bsonExtractIntegerField(doc, kTermFieldName, &_term); if (!status.isOK()) return status; const BSONElement hasDataElement = doc[kHasDataFieldName]; _hasDataSet = !hasDataElement.eoo(); _hasData = hasDataElement.trueValue(); const BSONElement rsConfigElement = doc[kConfigFieldName]; if (rsConfigElement.eoo()) { _configSet = false; _config = ReplicaSetConfig(); return Status::OK(); } else if (rsConfigElement.type() != Object) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Expected \"" << kConfigFieldName << "\" in response to replSetHeartbeat to have type " "Object, but found " << typeName(rsConfigElement.type())); } _configSet = true; return _config.initialize(rsConfigElement.Obj()); }
StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, const AndMatchExpression* andSoFar, const char* name, const BSONElement& e ) { // TODO: these should move to getGtLtOp, or its replacement if ( mongoutils::str::equals( "$eq", e.fieldName() ) ) return _parseComparison( name, new EqualityMatchExpression(), e ); if ( mongoutils::str::equals( "$not", e.fieldName() ) ) { return _parseNot( name, e ); } int x = e.getGtLtOp(-1); switch ( x ) { case -1: return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "unknown operator: " << e.fieldName() ); case BSONObj::LT: return _parseComparison( name, new LTMatchExpression(), e ); case BSONObj::LTE: return _parseComparison( name, new LTEMatchExpression(), e ); case BSONObj::GT: return _parseComparison( name, new GTMatchExpression(), e ); case BSONObj::GTE: return _parseComparison( name, new GTEMatchExpression(), e ); case BSONObj::NE: { StatusWithMatchExpression s = _parseComparison( name, new EqualityMatchExpression(), e ); if ( !s.isOK() ) return s; std::auto_ptr<NotMatchExpression> n( new NotMatchExpression() ); Status s2 = n->init( s.getValue() ); if ( !s2.isOK() ) return StatusWithMatchExpression( s2 ); return StatusWithMatchExpression( n.release() ); } case BSONObj::Equality: return _parseComparison( name, new EqualityMatchExpression(), e ); case BSONObj::opIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); std::auto_ptr<InMatchExpression> temp( new InMatchExpression() ); Status s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::NIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$nin needs an array" ); std::auto_ptr<InMatchExpression> temp( new InMatchExpression() ); Status s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); std::auto_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); s = temp2->init( temp.release() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp2.release() ); } case BSONObj::opSIZE: { int size = 0; if ( e.type() == String ) { // matching old odd semantics size = 0; } else if ( e.type() == NumberInt || e.type() == NumberLong ) { if (e.numberLong() < 0) { // SERVER-11952. Setting 'size' to -1 means that no documents // should match this $size expression. size = -1; } else { size = e.numberInt(); } } else if ( e.type() == NumberDouble ) { if ( e.numberInt() == e.numberDouble() ) { size = e.numberInt(); } else { // old semantcs require exact numeric match // so [1,2] != 1 or 2 size = -1; } } else { return StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); } std::auto_ptr<SizeMatchExpression> temp( new SizeMatchExpression() ); Status s = temp->init( name, size ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opEXISTS: { if ( e.eoo() ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$exists can't be eoo" ); std::auto_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); Status s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); if ( e.trueValue() ) return StatusWithMatchExpression( temp.release() ); std::auto_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); s = temp2->init( temp.release() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp2.release() ); } case BSONObj::opTYPE: { if ( !e.isNumber() ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$type has to be a number" ); int type = e.numberInt(); if ( e.type() != NumberInt && type != e.number() ) type = -1; std::auto_ptr<TypeMatchExpression> temp( new TypeMatchExpression() ); Status s = temp->init( name, type ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opMOD: return _parseMOD( name, e ); case BSONObj::opOPTIONS: { // TODO: try to optimize this // we have to do this since $options can be before or after a $regex // but we validate here BSONObjIterator i( context ); while ( i.more() ) { BSONElement temp = i.next(); if ( temp.getGtLtOp( -1 ) == BSONObj::opREGEX ) return StatusWithMatchExpression( NULL ); } return StatusWithMatchExpression( ErrorCodes::BadValue, "$options needs a $regex" ); } case BSONObj::opREGEX: { return _parseRegexDocument( name, context ); } case BSONObj::opELEM_MATCH: return _parseElemMatch( name, e ); case BSONObj::opALL: return _parseAll( name, e ); case BSONObj::opWITHIN: case BSONObj::opGEO_INTERSECTS: return expressionParserGeoCallback( name, x, context ); } return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "not handled: " << e.fieldName() ); }
// static Status ParsedProjection::make(const BSONObj& spec, const MatchExpression* const query, ParsedProjection** out, const ExtensionsCallback& extensionsCallback) { // Whether we're including or excluding fields. enum class IncludeExclude { kUninitialized, kInclude, kExclude }; IncludeExclude includeExclude = IncludeExclude::kUninitialized; bool requiresDocument = false; bool includeID = true; bool hasIndexKeyProjection = false; bool wantGeoNearPoint = false; bool wantGeoNearDistance = false; bool wantSortKey = false; // Until we see a positional or elemMatch operator we're normal. ArrayOpType arrayOpType = ARRAY_OP_NORMAL; BSONObjIterator it(spec); while (it.more()) { BSONElement e = it.next(); if (Object == e.type()) { BSONObj obj = e.embeddedObject(); if (1 != obj.nFields()) { return Status(ErrorCodes::BadValue, ">1 field in obj: " + obj.toString()); } BSONElement e2 = obj.firstElement(); if (mongoutils::str::equals(e2.fieldName(), "$slice")) { if (e2.isNumber()) { // This is A-OK. } else if (e2.type() == Array) { BSONObj arr = e2.embeddedObject(); if (2 != arr.nFields()) { return Status(ErrorCodes::BadValue, "$slice array wrong size"); } BSONObjIterator it(arr); // Skip over 'skip'. it.next(); int limit = it.next().numberInt(); if (limit <= 0) { return Status(ErrorCodes::BadValue, "$slice limit must be positive"); } } else { return Status(ErrorCodes::BadValue, "$slice only supports numbers and [skip, limit] arrays"); } // Projections with $slice aren't covered. requiresDocument = true; } else if (mongoutils::str::equals(e2.fieldName(), "$elemMatch")) { // Validate $elemMatch arguments and dependencies. if (Object != e2.type()) { return Status(ErrorCodes::BadValue, "elemMatch: Invalid argument, object required."); } if (ARRAY_OP_POSITIONAL == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify positional operator and $elemMatch."); } if (mongoutils::str::contains(e.fieldName(), '.')) { return Status(ErrorCodes::BadValue, "Cannot use $elemMatch projection on a nested field."); } arrayOpType = ARRAY_OP_ELEM_MATCH; // Create a MatchExpression for the elemMatch. BSONObj elemMatchObj = e.wrap(); verify(elemMatchObj.isOwned()); // TODO: Is there a faster way of validating the elemMatchObj? StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse(elemMatchObj, extensionsCallback); if (!statusWithMatcher.isOK()) { return statusWithMatcher.getStatus(); } // Projections with $elemMatch aren't covered. requiresDocument = true; } else if (mongoutils::str::equals(e2.fieldName(), "$meta")) { // Field for meta must be top level. We can relax this at some point. if (mongoutils::str::contains(e.fieldName(), '.')) { return Status(ErrorCodes::BadValue, "field for $meta cannot be nested"); } // Make sure the argument to $meta is something we recognize. // e.g. {x: {$meta: "textScore"}} if (String != e2.type()) { return Status(ErrorCodes::BadValue, "unexpected argument to $meta in proj"); } if (e2.valuestr() != LiteParsedQuery::metaTextScore && e2.valuestr() != LiteParsedQuery::metaRecordId && e2.valuestr() != LiteParsedQuery::metaIndexKey && e2.valuestr() != LiteParsedQuery::metaGeoNearDistance && e2.valuestr() != LiteParsedQuery::metaGeoNearPoint && e2.valuestr() != LiteParsedQuery::metaSortKey) { return Status(ErrorCodes::BadValue, "unsupported $meta operator: " + e2.str()); } // This clobbers everything else. if (e2.valuestr() == LiteParsedQuery::metaIndexKey) { hasIndexKeyProjection = true; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearDistance) { wantGeoNearDistance = true; } else if (e2.valuestr() == LiteParsedQuery::metaGeoNearPoint) { wantGeoNearPoint = true; } else if (e2.valuestr() == LiteParsedQuery::metaSortKey) { wantSortKey = true; } // Of the $meta projections, only sortKey can be covered. if (e2.valuestr() != LiteParsedQuery::metaSortKey) { requiresDocument = true; } } else { return Status(ErrorCodes::BadValue, string("Unsupported projection option: ") + e.toString()); } } else if (mongoutils::str::equals(e.fieldName(), "_id") && !e.trueValue()) { includeID = false; } else { // Projections of dotted fields aren't covered. if (mongoutils::str::contains(e.fieldName(), '.')) { requiresDocument = true; } // If we haven't specified an include/exclude, initialize includeExclude. We expect // further include/excludes to match it. if (includeExclude == IncludeExclude::kUninitialized) { includeExclude = e.trueValue() ? IncludeExclude::kInclude : IncludeExclude::kExclude; } else if ((includeExclude == IncludeExclude::kInclude && !e.trueValue()) || (includeExclude == IncludeExclude::kExclude && e.trueValue())) { return Status(ErrorCodes::BadValue, "Projection cannot have a mix of inclusion and exclusion."); } } if (_isPositionalOperator(e.fieldName())) { // Validate the positional op. if (!e.trueValue()) { return Status(ErrorCodes::BadValue, "Cannot exclude array elements with the positional operator."); } if (ARRAY_OP_POSITIONAL == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify more than one positional proj. per query."); } if (ARRAY_OP_ELEM_MATCH == arrayOpType) { return Status(ErrorCodes::BadValue, "Cannot specify positional operator and $elemMatch."); } std::string after = mongoutils::str::after(e.fieldName(), ".$"); if (mongoutils::str::contains(after, ".$")) { mongoutils::str::stream ss; ss << "Positional projection '" << e.fieldName() << "' contains " << "the positional operator more than once."; return Status(ErrorCodes::BadValue, ss); } std::string matchfield = mongoutils::str::before(e.fieldName(), '.'); if (!_hasPositionalOperatorMatch(query, matchfield)) { mongoutils::str::stream ss; ss << "Positional projection '" << e.fieldName() << "' does not " << "match the query document."; return Status(ErrorCodes::BadValue, ss); } arrayOpType = ARRAY_OP_POSITIONAL; } } // If includeExclude is uninitialized or set to exclude fields, then we can't use an index // because we don't know what fields we're missing. if (includeExclude == IncludeExclude::kUninitialized || includeExclude == IncludeExclude::kExclude) { requiresDocument = true; } // Fill out the returned obj. unique_ptr<ParsedProjection> pp(new ParsedProjection()); // The positional operator uses the MatchDetails from the query // expression to know which array element was matched. pp->_requiresMatchDetails = arrayOpType == ARRAY_OP_POSITIONAL; // Save the raw spec. It should be owned by the LiteParsedQuery. verify(spec.isOwned()); pp->_source = spec; pp->_returnKey = hasIndexKeyProjection; pp->_requiresDocument = requiresDocument; // Add meta-projections. pp->_wantGeoNearPoint = wantGeoNearPoint; pp->_wantGeoNearDistance = wantGeoNearDistance; pp->_wantSortKey = wantSortKey; // If it's possible to compute the projection in a covered fashion, populate _requiredFields // so the planner can perform projection analysis. if (!pp->_requiresDocument) { if (includeID) { pp->_requiredFields.push_back("_id"); } // The only way we could be here is if spec is only simple non-dotted-field inclusions or // the $meta sortKey projection. Therefore we can iterate over spec to get the fields // required. BSONObjIterator srcIt(spec); while (srcIt.more()) { BSONElement elt = srcIt.next(); // We've already handled the _id field before entering this loop. if (includeID && mongoutils::str::equals(elt.fieldName(), "_id")) { continue; } // $meta sortKey should not be checked as a part of _requiredFields, since it can // potentially produce a covered projection as long as the sort key is covered. if (BSONType::Object == elt.type()) { dassert(elt.Obj() == BSON("$meta" << "sortKey")); continue; } if (elt.trueValue()) { pp->_requiredFields.push_back(elt.fieldName()); } } } // returnKey clobbers everything except for sortKey meta-projection. if (hasIndexKeyProjection && !wantSortKey) { pp->_requiresDocument = false; } *out = pp.release(); return Status::OK(); }