summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Rassi <rassi@10gen.com>2014-01-09 17:19:12 -0500
committerJason Rassi <rassi@10gen.com>2014-01-09 17:28:13 -0500
commit80455be8a4c1e6e63e7236a51668b0e446748b91 (patch)
treef6b40ed4ed92b681ac3a5137036b5763b70150ad
parent07f1f4787c2f4fc713a180326683bfdea3bedcf5 (diff)
downloadmongo-80455be8a4c1e6e63e7236a51668b0e446748b91.tar.gz
SERVER-12153 SERVER-12171 Restrict valid query expressions with $text
CanonicalQuery now refuses to canonicalize queries that contain either: - >1 $text clause - $text inside a $nor clause
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/canonical_query.cpp32
-rw-r--r--src/mongo/db/query/canonical_query_test.cpp76
3 files changed, 107 insertions, 2 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index 0e60cb05970..f010afcdf15 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -99,6 +99,7 @@ env.CppUnitTest(
],
LIBDEPS=[
"query_planner",
+ "$BUILD_DIR/mongo/expressions_text",
],
)
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index 7668ab93f5f..af758af0b5a 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -30,6 +30,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/query/query_planner_common.h"
namespace mongo {
@@ -172,12 +173,39 @@ namespace mongo {
return sum;
}
+ /**
+ * 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.
- // TODO: We want $text in root level or within rooted AND for consistent text score
- // availability.
+ // 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.
diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp
index e95ea3a79b9..bc1be6ed5cd 100644
--- a/src/mongo/db/query/canonical_query_test.cpp
+++ b/src/mongo/db/query/canonical_query_test.cpp
@@ -47,6 +47,82 @@ namespace {
return StatusWithMatchExpression(CanonicalQuery::normalizeTree(swme.getValue()));
}
+ TEST(CanonicalQueryTest, IsValidText) {
+ auto_ptr<MatchExpression> me;
+ StatusWithMatchExpression swme(Status::OK());
+
+ // Valid: regular TEXT.
+ swme = parseNormalize("{$text: {$search: 's'}}");
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Valid: TEXT inside OR.
+ swme = parseNormalize(
+ "{$or: ["
+ " {$text: {$search: 's'}},"
+ " {a: 1}"
+ "]}"
+ );
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Valid: TEXT outside NOR.
+ swme = parseNormalize("{$text: {$search: 's'}, $nor: [{a: 1}, {b: 1}]}");
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Invalid: TEXT inside NOR.
+ swme = parseNormalize("{$nor: [{$text: {$search: 's'}}, {a: 1}]}");
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_NOT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Invalid: TEXT inside NOR.
+ swme = parseNormalize(
+ "{$nor: ["
+ " {$or: ["
+ " {$text: {$search: 's'}},"
+ " {a: 1}"
+ " ]},"
+ " {a: 2}"
+ "]}"
+ );
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_NOT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Invalid: >1 TEXT.
+ swme = parseNormalize(
+ "{$and: ["
+ " {$text: {$search: 's'}},"
+ " {$text: {$search: 't'}}"
+ "]}"
+ );
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_NOT_OK(CanonicalQuery::isValid(me.get()));
+
+ // Invalid: >1 TEXT.
+ swme = parseNormalize(
+ "{$and: ["
+ " {$or: ["
+ " {$text: {$search: 's'}},"
+ " {a: 1}"
+ " ]},"
+ " {$or: ["
+ " {$text: {$search: 't'}},"
+ " {b: 1}"
+ " ]}"
+ "]}"
+ );
+ ASSERT_OK(swme.getStatus());
+ me.reset(swme.getValue());
+ ASSERT_NOT_OK(CanonicalQuery::isValid(me.get()));
+ }
+
TEST(CanonicalQueryTest, IsValidGeo) {
auto_ptr<MatchExpression> me;
StatusWithMatchExpression swme(Status::OK());