summaryrefslogtreecommitdiff
path: root/src/mongo/db/fts
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2017-01-30 14:21:21 -0500
committerDavid Storch <david.storch@10gen.com>2017-02-01 13:21:24 -0500
commit521a57162820b3b1100d20408f5ad0cd89fdc80e (patch)
tree6690d0272a1161148315f8f7e24134291905ef75 /src/mongo/db/fts
parentccb8949e7bdd9f1e6c9f1d249d2482e64b07cab3 (diff)
downloadmongo-521a57162820b3b1100d20408f5ad0cd89fdc80e.tar.gz
SERVER-27392 fix bug in FTS index's check for indexed arrays
Diffstat (limited to 'src/mongo/db/fts')
-rw-r--r--src/mongo/db/fts/fts_index_format.cpp46
-rw-r--r--src/mongo/db/fts/fts_index_format_test.cpp76
2 files changed, 109 insertions, 13 deletions
diff --git a/src/mongo/db/fts/fts_index_format.cpp b/src/mongo/db/fts/fts_index_format.cpp
index 222b54aa2a0..770930e90c1 100644
--- a/src/mongo/db/fts/fts_index_format.cpp
+++ b/src/mongo/db/fts/fts_index_format.cpp
@@ -101,6 +101,30 @@ int guessTermSize(const std::string& term, TextIndexVersion textIndexVersion) {
return termKeyLengthV3;
}
}
+
+/**
+ * Given an object being indexed, 'obj', and a path through 'obj', returns the corresponding BSON
+ * element, according to the indexing rules for the non-text fields of an FTS index key pattern.
+ *
+ * Specifically, throws a user assertion if an array is encountered while traversing the 'path'. It
+ * is not legal for there to be an array along the path of the non-text prefix or suffix fields of a
+ * text index, unless a particular array index is specified, as in "a.3".
+ */
+BSONElement extractNonFTSKeyElement(const BSONObj& obj, StringData path) {
+ BSONElementSet indexedElements;
+ const bool expandArrayOnTrailingField = true;
+ std::set<size_t> arrayComponents;
+ dps::extractAllElementsAlongPath(
+ obj, path, indexedElements, expandArrayOnTrailingField, &arrayComponents);
+ uassert(ErrorCodes::CannotBuildIndexKeys,
+ str::stream() << "Field '" << path << "' of text index contains an array in document: "
+ << obj,
+ arrayComponents.empty());
+
+ // Since there aren't any arrays, there cannot be more than one extracted element on 'path'.
+ invariant(indexedElements.size() <= 1U);
+ return indexedElements.empty() ? nullElt : *indexedElements.begin();
+}
} // namespace
MONGO_INITIALIZER(FTSIndexFormat)(InitializerContext* context) {
@@ -116,24 +140,20 @@ void FTSIndexFormat::getKeys(const FTSSpec& spec, const BSONObj& obj, BSONObjSet
vector<BSONElement> extrasBefore;
vector<BSONElement> extrasAfter;
- // compute the non FTS key elements
+ // Compute the non FTS key elements for the prefix.
for (unsigned i = 0; i < spec.numExtraBefore(); i++) {
- BSONElement e = dps::extractElementAtPath(obj, spec.extraBefore(i));
- if (e.eoo())
- e = nullElt;
- uassert(16675, "cannot have a multi-key as a prefix to a text index", e.type() != Array);
- extrasBefore.push_back(e);
- extraSize += e.size();
+ auto indexedElement = extractNonFTSKeyElement(obj, spec.extraBefore(i));
+ extrasBefore.push_back(indexedElement);
+ extraSize += indexedElement.size();
}
+
+ // Compute the non FTS key elements for the suffix.
for (unsigned i = 0; i < spec.numExtraAfter(); i++) {
- BSONElement e = dps::extractElementAtPath(obj, spec.extraAfter(i));
- if (e.eoo())
- e = nullElt;
- extrasAfter.push_back(e);
- extraSize += e.size();
+ auto indexedElement = extractNonFTSKeyElement(obj, spec.extraAfter(i));
+ extrasAfter.push_back(indexedElement);
+ extraSize += indexedElement.size();
}
-
TermFrequencyMap term_freqs;
spec.scoreDocument(obj, &term_freqs);
diff --git a/src/mongo/db/fts/fts_index_format_test.cpp b/src/mongo/db/fts/fts_index_format_test.cpp
index c630f75ebd9..3a4d383bc45 100644
--- a/src/mongo/db/fts/fts_index_format_test.cpp
+++ b/src/mongo/db/fts/fts_index_format_test.cpp
@@ -34,6 +34,7 @@
#include <set>
+#include "mongo/bson/json.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/fts/fts_index_format.h"
#include "mongo/db/fts/fts_spec.h"
@@ -249,5 +250,80 @@ TEST(FTSIndexFormat, LongWordTextIndexVersion3) {
assertEqualsIndexKeys(expectedKeys, keys);
}
+
+TEST(FTSIndexFormat, GetKeysWithLeadingEmptyArrayThrows) {
+ BSONObj keyPattern = fromjson("{'a.b': 1, data: 'text'}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: {b: []}, data: 'foo'}");
+ ASSERT_THROWS_CODE(FTSIndexFormat::getKeys(spec, objToIndex, &keys),
+ UserException,
+ ErrorCodes::CannotBuildIndexKeys);
+}
+
+TEST(FTSIndexFormat, GetKeysWithTrailingEmptyArrayThrows) {
+ BSONObj keyPattern = fromjson("{data: 'text', 'a.b': 1}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: {b: []}, data: 'foo'}");
+ ASSERT_THROWS_CODE(FTSIndexFormat::getKeys(spec, objToIndex, &keys),
+ UserException,
+ ErrorCodes::CannotBuildIndexKeys);
+}
+
+TEST(FTSIndexFormat, GetKeysWithLeadingSingleElementArrayThrows) {
+ BSONObj keyPattern = fromjson("{'a.b': 1, data: 'text'}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: [{b: 9}], data: 'foo'}");
+ ASSERT_THROWS_CODE(FTSIndexFormat::getKeys(spec, objToIndex, &keys),
+ UserException,
+ ErrorCodes::CannotBuildIndexKeys);
+}
+
+TEST(FTSIndexFormat, GetKeysWithTrailingSingleElementArrayThrows) {
+ BSONObj keyPattern = fromjson("{data: 'text', 'a.b': 1}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: [{b: 9}], data: 'foo'}");
+ ASSERT_THROWS_CODE(FTSIndexFormat::getKeys(spec, objToIndex, &keys),
+ UserException,
+ ErrorCodes::CannotBuildIndexKeys);
}
+
+TEST(FTSIndexFormat, GetKeysWithMultiElementArrayThrows) {
+ BSONObj keyPattern = fromjson("{'a.b': 1, 'a.c': 'text'}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: [{b: 9, c: 'foo'}, {b: 10, c: 'bar'}]}");
+ ASSERT_THROWS_CODE(FTSIndexFormat::getKeys(spec, objToIndex, &keys),
+ UserException,
+ ErrorCodes::CannotBuildIndexKeys);
+}
+
+TEST(FTSIndexFormat, GetKeysWithPositionalPathAllowed) {
+ BSONObj keyPattern = fromjson("{'a.0': 1, 'a.b': 'text'}");
+ FTSSpec spec(assertGet(FTSSpec::fixSpec(BSON("key" << keyPattern << "textIndexVersion" << 3))));
+ BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
+ BSONObj objToIndex = fromjson("{a: [{b: 'foo'}, {b: 'bar'}]}");
+ FTSIndexFormat::getKeys(spec, objToIndex, &keys);
+ ASSERT_EQ(2U, keys.size());
+
+ {
+ BSONObj key = *(keys.begin());
+ ASSERT_EQ(3, key.nFields());
+ BSONObjIterator it{key};
+ ASSERT_BSONELT_EQ(it.next(), fromjson("{'': {b: 'foo'}}").firstElement());
+ ASSERT_BSONELT_EQ(it.next(), fromjson("{'': 'bar'}").firstElement());
+ }
+
+ {
+ BSONObj key = *(++keys.begin());
+ ASSERT_EQ(3, key.nFields());
+ BSONObjIterator it{key};
+ ASSERT_BSONELT_EQ(it.next(), fromjson("{'': {b: 'foo'}}").firstElement());
+ ASSERT_BSONELT_EQ(it.next(), fromjson("{'': 'foo'}").firstElement());
+ }
}
+} // namespace fts
+} // namespace mongo