diff options
Diffstat (limited to 'src/mongo/db/pipeline/document_source_match.cpp')
-rw-r--r-- | src/mongo/db/pipeline/document_source_match.cpp | 355 |
1 files changed, 179 insertions, 176 deletions
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index b6c6e005446..50ef5e95eb5 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -39,121 +39,123 @@ namespace mongo { - using boost::intrusive_ptr; - using std::string; - using std::vector; +using boost::intrusive_ptr; +using std::string; +using std::vector; - const char DocumentSourceMatch::matchName[] = "$match"; +const char DocumentSourceMatch::matchName[] = "$match"; - const char *DocumentSourceMatch::getSourceName() const { - return matchName; - } - - Value DocumentSourceMatch::serialize(bool explain) const { - return Value(DOC(getSourceName() << Document(getQuery()))); - } +const char* DocumentSourceMatch::getSourceName() const { + return matchName; +} - intrusive_ptr<DocumentSource> DocumentSourceMatch::optimize() { - return getQuery().isEmpty() ? nullptr : this; - } +Value DocumentSourceMatch::serialize(bool explain) const { + return Value(DOC(getSourceName() << Document(getQuery()))); +} - boost::optional<Document> DocumentSourceMatch::getNext() { - pExpCtx->checkForInterrupt(); +intrusive_ptr<DocumentSource> DocumentSourceMatch::optimize() { + return getQuery().isEmpty() ? nullptr : this; +} - // The user facing error should have been generated earlier. - massert(17309, "Should never call getNext on a $match stage with $text clause", - !_isTextQuery); +boost::optional<Document> DocumentSourceMatch::getNext() { + pExpCtx->checkForInterrupt(); - while (boost::optional<Document> next = pSource->getNext()) { - // The matcher only takes BSON documents, so we have to make one. - if (matcher->matches(next->toBson())) - return next; - } + // The user facing error should have been generated earlier. + massert(17309, "Should never call getNext on a $match stage with $text clause", !_isTextQuery); - // Nothing matched - return boost::none; + while (boost::optional<Document> next = pSource->getNext()) { + // The matcher only takes BSON documents, so we have to make one. + if (matcher->matches(next->toBson())) + return next; } - bool DocumentSourceMatch::coalesce(const intrusive_ptr<DocumentSource>& nextSource) { - DocumentSourceMatch* otherMatch = dynamic_cast<DocumentSourceMatch*>(nextSource.get()); - if (!otherMatch) - return false; + // Nothing matched + return boost::none; +} - if (otherMatch->_isTextQuery) { - // Non-initial text queries are disallowed (enforced by setSource below). This prevents - // "hiding" a non-initial text query by combining it with another match. - return false; +bool DocumentSourceMatch::coalesce(const intrusive_ptr<DocumentSource>& nextSource) { + DocumentSourceMatch* otherMatch = dynamic_cast<DocumentSourceMatch*>(nextSource.get()); + if (!otherMatch) + return false; - // The rest of this block is for once we support non-initial text queries. + if (otherMatch->_isTextQuery) { + // Non-initial text queries are disallowed (enforced by setSource below). This prevents + // "hiding" a non-initial text query by combining it with another match. + return false; - if (_isTextQuery) { - // The score should only come from the last $match. We can't combine since then this - // match's score would impact otherMatch's. - return false; - } + // The rest of this block is for once we support non-initial text queries. - _isTextQuery = true; + if (_isTextQuery) { + // The score should only come from the last $match. We can't combine since then this + // match's score would impact otherMatch's. + return false; } - // Replace our matcher with the $and of ours and theirs. - matcher.reset(new Matcher(BSON("$and" << BSON_ARRAY(getQuery() - << otherMatch->getQuery())), - MatchExpressionParser::WhereCallback())); - - return true; + _isTextQuery = true; } + // Replace our matcher with the $and of ours and theirs. + matcher.reset(new Matcher(BSON("$and" << BSON_ARRAY(getQuery() << otherMatch->getQuery())), + MatchExpressionParser::WhereCallback())); + + return true; +} + namespace { - // This block contains the functions that make up the implementation of - // DocumentSourceMatch::redactSafePortion(). They will only be called after - // the Match expression has been successfully parsed so they can assume that - // input is well formed. +// This block contains the functions that make up the implementation of +// DocumentSourceMatch::redactSafePortion(). They will only be called after +// the Match expression has been successfully parsed so they can assume that +// input is well formed. - bool isAllDigits(StringData str) { - if (str.empty()) - return false; +bool isAllDigits(StringData str) { + if (str.empty()) + return false; - for (size_t i=0; i < str.size(); i++) { - if (!isdigit(str[i])) - return false; - } - return true; + for (size_t i = 0; i < str.size(); i++) { + if (!isdigit(str[i])) + return false; } + return true; +} - bool isFieldnameRedactSafe(StringData fieldName) { - // Can't have numeric elements in the dotted path since redacting elements from an array - // would change the indexes. +bool isFieldnameRedactSafe(StringData fieldName) { + // Can't have numeric elements in the dotted path since redacting elements from an array + // would change the indexes. - const size_t dotPos = fieldName.find('.'); - if (dotPos == string::npos) - return !isAllDigits(fieldName); + const size_t dotPos = fieldName.find('.'); + if (dotPos == string::npos) + return !isAllDigits(fieldName); - const StringData part = fieldName.substr(0, dotPos); - const StringData rest = fieldName.substr(dotPos + 1); - return !isAllDigits(part) && isFieldnameRedactSafe(rest); - } + const StringData part = fieldName.substr(0, dotPos); + const StringData rest = fieldName.substr(dotPos + 1); + return !isAllDigits(part) && isFieldnameRedactSafe(rest); +} - bool isTypeRedactSafeInComparison(BSONType type) { - if (type == Array) return false; - if (type == Object) return false; - if (type == jstNULL) return false; - if (type == Undefined) return false; // Currently a Matcher parse error. +bool isTypeRedactSafeInComparison(BSONType type) { + if (type == Array) + return false; + if (type == Object) + return false; + if (type == jstNULL) + return false; + if (type == Undefined) + return false; // Currently a Matcher parse error. - return true; - } + return true; +} - Document redactSafePortionTopLevel(BSONObj query); // mutually recursive with next function +Document redactSafePortionTopLevel(BSONObj query); // mutually recursive with next function - // Returns the redact-safe portion of an "inner" match expression. This is the layer like - // {$gt: 5} which does not include the field name. Returns an empty document if none of the - // expression can safely be promoted in front of a $redact. - Document redactSafePortionDollarOps(BSONObj expr) { - MutableDocument output; - BSONForEach(field, expr) { - if (field.fieldName()[0] != '$') - continue; +// Returns the redact-safe portion of an "inner" match expression. This is the layer like +// {$gt: 5} which does not include the field name. Returns an empty document if none of the +// expression can safely be promoted in front of a $redact. +Document redactSafePortionDollarOps(BSONObj expr) { + MutableDocument output; + BSONForEach(field, expr) { + if (field.fieldName()[0] != '$') + continue; - switch(BSONObj::MatchType(field.getGtLtOp(BSONObj::Equality))) { + switch (BSONObj::MatchType(field.getGtLtOp(BSONObj::Equality))) { // These are always ok case BSONObj::opTYPE: case BSONObj::opREGEX: @@ -218,7 +220,7 @@ namespace { } // These are never allowed - case BSONObj::Equality: // This actually means unknown + case BSONObj::Equality: // This actually means unknown case BSONObj::opMAX_DISTANCE: case BSONObj::opNEAR: case BSONObj::NE: @@ -228,55 +230,57 @@ namespace { case BSONObj::opWITHIN: case BSONObj::opGEO_INTERSECTS: continue; - } } - return output.freeze(); } + return output.freeze(); +} - // Returns the redact-safe portion of an "outer" match expression. This is the layer like - // {fieldName: {...}} which does include the field name. Returns an empty document if none of - // the expression can safely be promoted in front of a $redact. - Document redactSafePortionTopLevel(BSONObj query) { - MutableDocument output; - BSONForEach(field, query) { - if (field.fieldName()[0] == '$') { - if (str::equals(field.fieldName(), "$or")) { - // $or must be all-or-nothing (line $in). Can't include subset of elements. - vector<Value> okClauses; - BSONForEach(elem, field.Obj()) { - Document clause = redactSafePortionTopLevel(elem.Obj()); - if (clause.empty()) { - okClauses.clear(); - break; - } - okClauses.push_back(Value(clause)); - } - - if (!okClauses.empty()) - output["$or"] = Value(std::move(okClauses)); - } - else if (str::equals(field.fieldName(), "$and")) { - // $and can include subset of elements (like $all). - vector<Value> okClauses; - BSONForEach(elem, field.Obj()) { - Document clause = redactSafePortionTopLevel(elem.Obj()); - if (!clause.empty()) - okClauses.push_back(Value(clause)); +// Returns the redact-safe portion of an "outer" match expression. This is the layer like +// {fieldName: {...}} which does include the field name. Returns an empty document if none of +// the expression can safely be promoted in front of a $redact. +Document redactSafePortionTopLevel(BSONObj query) { + MutableDocument output; + BSONForEach(field, query) { + if (field.fieldName()[0] == '$') { + if (str::equals(field.fieldName(), "$or")) { + // $or must be all-or-nothing (line $in). Can't include subset of elements. + vector<Value> okClauses; + BSONForEach(elem, field.Obj()) { + Document clause = redactSafePortionTopLevel(elem.Obj()); + if (clause.empty()) { + okClauses.clear(); + break; } - if (!okClauses.empty()) - output["$and"] = Value(std::move(okClauses)); + okClauses.push_back(Value(clause)); } - continue; + if (!okClauses.empty()) + output["$or"] = Value(std::move(okClauses)); + } else if (str::equals(field.fieldName(), "$and")) { + // $and can include subset of elements (like $all). + vector<Value> okClauses; + BSONForEach(elem, field.Obj()) { + Document clause = redactSafePortionTopLevel(elem.Obj()); + if (!clause.empty()) + okClauses.push_back(Value(clause)); + } + if (!okClauses.empty()) + output["$and"] = Value(std::move(okClauses)); } - if (!isFieldnameRedactSafe(field.fieldNameStringData())) - continue; + continue; + } + + if (!isFieldnameRedactSafe(field.fieldNameStringData())) + continue; - switch (field.type()) { - case Array: continue; // exact matches on arrays are never allowed - case jstNULL: continue; // can't look for missing fields - case Undefined: continue; // Currently a Matcher parse error. + switch (field.type()) { + case Array: + continue; // exact matches on arrays are never allowed + case jstNULL: + continue; // can't look for missing fields + case Undefined: + continue; // Currently a Matcher parse error. case Object: { Document sub = redactSafePortionDollarOps(field.Obj()); @@ -290,69 +294,68 @@ namespace { default: output[field.fieldNameStringData()] = Value(field); break; - } } - return output.freeze(); } + return output.freeze(); +} } - BSONObj DocumentSourceMatch::redactSafePortion() const { - return redactSafePortionTopLevel(getQuery()).toBson(); - } +BSONObj DocumentSourceMatch::redactSafePortion() const { + return redactSafePortionTopLevel(getQuery()).toBson(); +} - void DocumentSourceMatch::setSource(DocumentSource* source) { - uassert(17313, "$match with $text is only allowed as the first pipeline stage", - !_isTextQuery); +void DocumentSourceMatch::setSource(DocumentSource* source) { + uassert(17313, "$match with $text is only allowed as the first pipeline stage", !_isTextQuery); - DocumentSource::setSource(source); - } + DocumentSource::setSource(source); +} - bool DocumentSourceMatch::isTextQuery(const BSONObj& query) { - BSONForEach(e, query) { - const StringData fieldName = e.fieldNameStringData(); - if (fieldName == StringData("$text", StringData::LiteralTag())) - return true; +bool DocumentSourceMatch::isTextQuery(const BSONObj& query) { + BSONForEach(e, query) { + const StringData fieldName = e.fieldNameStringData(); + if (fieldName == StringData("$text", StringData::LiteralTag())) + return true; - if (e.isABSONObj() && isTextQuery(e.Obj())) - return true; - } - return false; + if (e.isABSONObj() && isTextQuery(e.Obj())) + return true; } + return false; +} - static void uassertNoDisallowedClauses(BSONObj query) { - BSONForEach(e, query) { - // can't use the Matcher API because this would segfault the constructor - uassert(16395, "$where is not allowed inside of a $match aggregation expression", - ! str::equals(e.fieldName(), "$where")); - // geo breaks if it is not the first portion of the pipeline - uassert(16424, "$near is not allowed inside of a $match aggregation expression", - ! str::equals(e.fieldName(), "$near")); - uassert(16426, "$nearSphere is not allowed inside of a $match aggregation expression", - ! str::equals(e.fieldName(), "$nearSphere")); - if (e.isABSONObj()) - uassertNoDisallowedClauses(e.Obj()); - } +static void uassertNoDisallowedClauses(BSONObj query) { + BSONForEach(e, query) { + // can't use the Matcher API because this would segfault the constructor + uassert(16395, + "$where is not allowed inside of a $match aggregation expression", + !str::equals(e.fieldName(), "$where")); + // geo breaks if it is not the first portion of the pipeline + uassert(16424, + "$near is not allowed inside of a $match aggregation expression", + !str::equals(e.fieldName(), "$near")); + uassert(16426, + "$nearSphere is not allowed inside of a $match aggregation expression", + !str::equals(e.fieldName(), "$nearSphere")); + if (e.isABSONObj()) + uassertNoDisallowedClauses(e.Obj()); } +} - intrusive_ptr<DocumentSource> DocumentSourceMatch::createFromBson( - BSONElement elem, - const intrusive_ptr<ExpressionContext> &pExpCtx) { - uassert(15959, "the match filter must be an expression in an object", - elem.type() == Object); +intrusive_ptr<DocumentSource> DocumentSourceMatch::createFromBson( + BSONElement elem, const intrusive_ptr<ExpressionContext>& pExpCtx) { + uassert(15959, "the match filter must be an expression in an object", elem.type() == Object); - uassertNoDisallowedClauses(elem.Obj()); + uassertNoDisallowedClauses(elem.Obj()); - return new DocumentSourceMatch(elem.Obj(), pExpCtx); - } + return new DocumentSourceMatch(elem.Obj(), pExpCtx); +} - BSONObj DocumentSourceMatch::getQuery() const { - return *(matcher->getQuery()); - } +BSONObj DocumentSourceMatch::getQuery() const { + return *(matcher->getQuery()); +} - DocumentSourceMatch::DocumentSourceMatch(const BSONObj &query, - const intrusive_ptr<ExpressionContext> &pExpCtx) - : DocumentSource(pExpCtx), - matcher(new Matcher(query.getOwned(), MatchExpressionParser::WhereCallback())), - _isTextQuery(isTextQuery(query)) - {} +DocumentSourceMatch::DocumentSourceMatch(const BSONObj& query, + const intrusive_ptr<ExpressionContext>& pExpCtx) + : DocumentSource(pExpCtx), + matcher(new Matcher(query.getOwned(), MatchExpressionParser::WhereCallback())), + _isTextQuery(isTextQuery(query)) {} } |