/** * Does 'root' have a subtree of type 'subtreeType' with a node of type 'childType' inside? */ bool hasNodeInSubtree(MatchExpression* root, MatchExpression::MatchType childType, MatchExpression::MatchType subtreeType) { if (subtreeType == root->matchType()) { return QueryPlannerCommon::hasNode(root, childType); } for (size_t i = 0; i < root->numChildren(); ++i) { if (hasNodeInSubtree(root->getChild(i), childType, subtreeType)) { return true; } } return false; }
// static Status CanonicalQuery::isValid(MatchExpression* root) { // Analysis below should be done after squashing the tree to make it clearer. // There can only be one TEXT. If there is a TEXT, it cannot appear inside a NOR. // // Note that the query grammar (as enforced by the MatchExpression parser) forbids TEXT // inside of value-expression clauses like NOT, so we don't check those here. size_t numText = countNodes(root, MatchExpression::TEXT); if (numText > 1) { return Status(ErrorCodes::BadValue, "Too many text expressions"); } else if (1 == numText) { if (hasNodeInSubtree(root, MatchExpression::TEXT, MatchExpression::NOR)) { return Status(ErrorCodes::BadValue, "text expression not allowed in nor"); } } // There can only be one NEAR. If there is a NEAR, it must be either the root or the root // must be an AND and its child must be a NEAR. size_t numGeoNear = countNodes(root, MatchExpression::GEO_NEAR); if (numGeoNear > 1) { return Status(ErrorCodes::BadValue, "Too many geoNear expressions"); } else if (1 == numGeoNear) { bool topLevel = false; if (MatchExpression::GEO_NEAR == root->matchType()) { topLevel = true; } else if (MatchExpression::AND == root->matchType()) { for (size_t i = 0; i < root->numChildren(); ++i) { if (MatchExpression::GEO_NEAR == root->getChild(i)->matchType()) { topLevel = true; break; } } } if (!topLevel) { return Status(ErrorCodes::BadValue, "geoNear must be top-level expr"); } } // TEXT and NEAR cannot both be in the query. if (numText > 0 && numGeoNear > 0) { return Status(ErrorCodes::BadValue, "text and geoNear not allowed in same query"); } return Status::OK(); }
// static Status CanonicalQuery::isValid(MatchExpression* root, const LiteParsedQuery& parsed) { // Analysis below should be done after squashing the tree to make it clearer. // There can only be one TEXT. If there is a TEXT, it cannot appear inside a NOR. // // Note that the query grammar (as enforced by the MatchExpression parser) forbids TEXT // inside of value-expression clauses like NOT, so we don't check those here. size_t numText = countNodes(root, MatchExpression::TEXT); if (numText > 1) { return Status(ErrorCodes::BadValue, "Too many text expressions"); } else if (1 == numText) { if (hasNodeInSubtree(root, MatchExpression::TEXT, MatchExpression::NOR)) { return Status(ErrorCodes::BadValue, "text expression not allowed in nor"); } } // There can only be one NEAR. If there is a NEAR, it must be either the root or the root // must be an AND and its child must be a NEAR. size_t numGeoNear = countNodes(root, MatchExpression::GEO_NEAR); if (numGeoNear > 1) { return Status(ErrorCodes::BadValue, "Too many geoNear expressions"); } else if (1 == numGeoNear) { bool topLevel = false; if (MatchExpression::GEO_NEAR == root->matchType()) { topLevel = true; } else if (MatchExpression::AND == root->matchType()) { for (size_t i = 0; i < root->numChildren(); ++i) { if (MatchExpression::GEO_NEAR == root->getChild(i)->matchType()) { topLevel = true; break; } } } if (!topLevel) { return Status(ErrorCodes::BadValue, "geoNear must be top-level expr"); } } // NEAR cannot have a $natural sort or $natural hint. if (numGeoNear > 0) { BSONObj sortObj = parsed.getSort(); if (!sortObj["$natural"].eoo()) { return Status(ErrorCodes::BadValue, "geoNear expression not allowed with $natural sort order"); } BSONObj hintObj = parsed.getHint(); if (!hintObj["$natural"].eoo()) { return Status(ErrorCodes::BadValue, "geoNear expression not allowed with $natural hint"); } } // TEXT and NEAR cannot both be in the query. if (numText > 0 && numGeoNear > 0) { return Status(ErrorCodes::BadValue, "text and geoNear not allowed in same query"); } // TEXT and {$natural: ...} sort order cannot both be in the query. if (numText > 0) { const BSONObj& sortObj = parsed.getSort(); BSONObjIterator it(sortObj); while (it.more()) { BSONElement elt = it.next(); if (str::equals("$natural", elt.fieldName())) { return Status(ErrorCodes::BadValue, "text expression not allowed with $natural sort order"); } } } // TEXT and hint cannot both be in the query. if (numText > 0 && !parsed.getHint().isEmpty()) { return Status(ErrorCodes::BadValue, "text and hint not allowed in same query"); } // TEXT and snapshot cannot both be in the query. if (numText > 0 && parsed.isSnapshot()) { return Status(ErrorCodes::BadValue, "text and snapshot not allowed in same query"); } return Status::OK(); }
// static Status CanonicalQuery::isValid(MatchExpression* root, const QueryRequest& parsed) { // Analysis below should be done after squashing the tree to make it clearer. // There can only be one TEXT. If there is a TEXT, it cannot appear inside a NOR. // // Note that the query grammar (as enforced by the MatchExpression parser) forbids TEXT // inside of value-expression clauses like NOT, so we don't check those here. size_t numText = countNodes(root, MatchExpression::TEXT); if (numText > 1) { return Status(ErrorCodes::BadValue, "Too many text expressions"); } else if (1 == numText) { if (hasNodeInSubtree(root, MatchExpression::TEXT, MatchExpression::NOR)) { return Status(ErrorCodes::BadValue, "text expression not allowed in nor"); } } // There can only be one NEAR. If there is a NEAR, it must be either the root or the root // must be an AND and its child must be a NEAR. size_t numGeoNear = countNodes(root, MatchExpression::GEO_NEAR); if (numGeoNear > 1) { return Status(ErrorCodes::BadValue, "Too many geoNear expressions"); } else if (1 == numGeoNear) { bool topLevel = false; if (MatchExpression::GEO_NEAR == root->matchType()) { topLevel = true; } else if (MatchExpression::AND == root->matchType()) { for (size_t i = 0; i < root->numChildren(); ++i) { if (MatchExpression::GEO_NEAR == root->getChild(i)->matchType()) { topLevel = true; break; } } } if (!topLevel) { return Status(ErrorCodes::BadValue, "geoNear must be top-level expr"); } } // NEAR cannot have a $natural sort or $natural hint. const BSONObj& sortObj = parsed.getSort(); BSONElement sortNaturalElt = sortObj["$natural"]; const BSONObj& hintObj = parsed.getHint(); BSONElement hintNaturalElt = hintObj["$natural"]; if (numGeoNear > 0) { if (sortNaturalElt) { return Status(ErrorCodes::BadValue, "geoNear expression not allowed with $natural sort order"); } if (hintNaturalElt) { return Status(ErrorCodes::BadValue, "geoNear expression not allowed with $natural hint"); } } // TEXT and NEAR cannot both be in the query. if (numText > 0 && numGeoNear > 0) { return Status(ErrorCodes::BadValue, "text and geoNear not allowed in same query"); } // TEXT and {$natural: ...} sort order cannot both be in the query. if (numText > 0 && sortNaturalElt) { return Status(ErrorCodes::BadValue, "text expression not allowed with $natural sort order"); } // TEXT and hint cannot both be in the query. if (numText > 0 && !hintObj.isEmpty()) { return Status(ErrorCodes::BadValue, "text and hint not allowed in same query"); } // TEXT and snapshot cannot both be in the query. if (numText > 0 && parsed.isSnapshot()) { return Status(ErrorCodes::BadValue, "text and snapshot not allowed in same query"); } // TEXT and tailable are incompatible. if (numText > 0 && parsed.isTailable()) { return Status(ErrorCodes::BadValue, "text and tailable cursor not allowed in same query"); } // $natural sort order must agree with hint. if (sortNaturalElt) { if (!hintObj.isEmpty() && !hintNaturalElt) { return Status(ErrorCodes::BadValue, "index hint not allowed with $natural sort order"); } if (hintNaturalElt) { if (hintNaturalElt.numberInt() != sortNaturalElt.numberInt()) { return Status(ErrorCodes::BadValue, "$natural hint must be in the same direction as $natural sort order"); } } } return Status::OK(); }