diff options
author | David Storch <david.storch@10gen.com> | 2017-01-30 14:21:21 -0500 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2017-02-01 13:21:24 -0500 |
commit | 521a57162820b3b1100d20408f5ad0cd89fdc80e (patch) | |
tree | 6690d0272a1161148315f8f7e24134291905ef75 /src/mongo/db/fts | |
parent | ccb8949e7bdd9f1e6c9f1d249d2482e64b07cab3 (diff) | |
download | mongo-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.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/fts/fts_index_format_test.cpp | 76 |
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 |