summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-09-06 16:30:44 +0100
committerBernard Gorman <bernard.gorman@gmail.com>2018-09-14 21:06:17 +0100
commitf8c5f009ac1def37d73b20d45dd9352e36849cb9 (patch)
treeea087702639c07b14676d5576db8189eed009a95 /src/mongo/db/query
parent45fccee20b37579662fabc6268a76a52f00661c4 (diff)
downloadmongo-f8c5f009ac1def37d73b20d45dd9352e36849cb9.tar.gz
SERVER-36362 Do not consider an 'allPaths' index for a $text query
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp29
-rw-r--r--src/mongo/db/query/planner_ixselect.h11
-rw-r--r--src/mongo/db/query/query_planner_all_paths_index_test.cpp40
3 files changed, 74 insertions, 6 deletions
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index eb9cbfbb55f..70deacf2d5b 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -686,6 +686,7 @@ void QueryPlannerIXSelect::_rateIndices(MatchExpression* node,
// static
void QueryPlannerIXSelect::stripInvalidAssignments(MatchExpression* node,
const vector<IndexEntry>& indices) {
+ stripInvalidAssignmentsToAllPathsIndexes(node, indices);
stripInvalidAssignmentsToTextIndexes(node, indices);
if (MatchExpression::GEO != node->matchType() &&
@@ -874,6 +875,34 @@ void QueryPlannerIXSelect::stripInvalidAssignmentsToPartialIndices(
}
//
+// AllPaths index invalid assignments.
+//
+void QueryPlannerIXSelect::stripInvalidAssignmentsToAllPathsIndexes(
+ MatchExpression* root, const vector<IndexEntry>& indices) {
+ for (size_t idx = 0; idx < indices.size(); ++idx) {
+ // Skip over all indexes except $**.
+ if (indices[idx].type != IndexType::INDEX_ALLPATHS) {
+ continue;
+ }
+ // If we have a $** index, check whether we have a TEXT node in the MatchExpression tree.
+ const std::function<MatchExpression*(MatchExpression*)> findTextNode = [&](auto* node) {
+ if (node->matchType() == MatchExpression::TEXT) {
+ return node;
+ }
+ for (size_t i = 0; i < node->numChildren(); ++i) {
+ if (auto* foundNode = findTextNode(node->getChild(i)))
+ return foundNode;
+ }
+ return static_cast<MatchExpression*>(nullptr);
+ };
+ // If so, remove the $** index from the node's relevant tags.
+ if (auto* textNode = findTextNode(root)) {
+ removeIndexRelevantTag(textNode, idx);
+ }
+ }
+}
+
+//
// Text index quirks
//
diff --git a/src/mongo/db/query/planner_ixselect.h b/src/mongo/db/query/planner_ixselect.h
index b1834a1610e..b0181fab150 100644
--- a/src/mongo/db/query/planner_ixselect.h
+++ b/src/mongo/db/query/planner_ixselect.h
@@ -228,6 +228,17 @@ private:
const std::vector<IndexEntry>& indices);
/**
+ * This function strips RelevantTag assignments to expanded 'allPaths' indexes, in cases where
+ * the assignment is incompatible with the query.
+ *
+ * Specifically, if the query has a TEXT node with both 'text' and 'allPaths' indexes present,
+ * then the 'allPaths' index will mark itself as relevant to the '_fts' path reported by the
+ * TEXT node. We therefore remove any such misassigned 'allPaths' tags here.
+ */
+ static void stripInvalidAssignmentsToAllPathsIndexes(MatchExpression* root,
+ const std::vector<IndexEntry>& indices);
+
+ /**
* This function strips RelevantTag assignments to partial indices, where the assignment is
* incompatible with the index's filter expression.
*
diff --git a/src/mongo/db/query/query_planner_all_paths_index_test.cpp b/src/mongo/db/query/query_planner_all_paths_index_test.cpp
index 0e6374252c1..c758a0c48a8 100644
--- a/src/mongo/db/query/query_planner_all_paths_index_test.cpp
+++ b/src/mongo/db/query/query_planner_all_paths_index_test.cpp
@@ -585,12 +585,6 @@ TEST_F(QueryPlannerAllPathsTest, InBasicOrEquivalent) {
"bounds: {'$_path': [['a','a',true,true]], a: [[1,1,true,true],[2,2,true,true]]}}}}}");
}
-// TODO SERVER-35335: Add testing for Min/Max.
-// TODO SERVER-36517: Add testing for DISTINCT_SCAN.
-// TODO SERVER-35336: Add testing for partialFilterExpression.
-// TODO SERVER-35331: Add testing for hints.
-// TODO SERVER-36145: Add testing for non-blocking sort.
-
//
// Index intersection tests.
//
@@ -668,4 +662,38 @@ TEST_F(QueryPlannerAllPathsTest, AllPathsIndexDoesNotParticipateInIndexIntersect
"{fetch: {filter:{b:{$gt:10}}, node: {ixscan: {filter: null, pattern: {$_path:1, a:1}}}}}");
}
+//
+// AllPaths and $text index tests.
+//
+
+TEST_F(QueryPlannerAllPathsTest, AllPathsIndexDoesNotSupplyCandidatePlanForTextSearch) {
+ addAllPathsIndex(BSON("$**" << 1));
+ addIndex(BSON("a" << 1 << "_fts"
+ << "text"
+ << "_ftsx"
+ << 1));
+
+ // Confirm that the allPaths index generates candidate plans for queries which do not include a
+ // $text predicate.
+ runQuery(fromjson("{a: 10, b: 10}"));
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, a: 1}}}}}");
+ assertSolutionExists(
+ "{fetch: {filter: {a: 10}, node: {ixscan: {filter: null, pattern: {'$_path': 1, b: 1}}}}}");
+
+ // Confirm that the allPaths index does not produce any candidate plans when a query includes a
+ // $text predicate, even for non-$text predicates which may be present in the query.
+ runQuery(fromjson("{a: 10, b: 10, $text: {$search: 'banana'}}"));
+ ASSERT_EQUALS(getNumSolutions(), 1U);
+ assertSolutionExists(
+ "{fetch: {filter: {b: 10}, node: {text: {prefix: {a: 10}, search: 'banana'}}}}");
+}
+
+// TODO SERVER-35335: Add testing for Min/Max.
+// TODO SERVER-36517: Add testing for DISTINCT_SCAN.
+// TODO SERVER-35336: Add testing for partialFilterExpression.
+// TODO SERVER-35331: Add testing for hints.
+// TODO SERVER-36145: Add testing for non-blocking sort.
+
} // namespace mongo