diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/query/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_collator_test.cpp | 682 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_eq_null_test.cpp | 431 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_interval_test.cpp | 231 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_regex_test.cpp | 335 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_test.cpp | 1920 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_test.h | 162 | ||||
-rw-r--r-- | src/mongo/db/query/index_bounds_builder_type_test.cpp | 158 |
8 files changed, 2086 insertions, 1838 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 876ab2b8c77..95463e76357 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -257,7 +257,12 @@ env.CppUnitTest( "get_executor_test.cpp", "getmore_request_test.cpp", "hint_parser_test.cpp", + "index_bounds_builder_collator_test.cpp", + "index_bounds_builder_eq_null_test.cpp", + "index_bounds_builder_interval_test.cpp", + "index_bounds_builder_regex_test.cpp", "index_bounds_builder_test.cpp", + "index_bounds_builder_type_test.cpp", "index_bounds_test.cpp", "index_entry_test.cpp", "interval_test.cpp", diff --git a/src/mongo/db/query/index_bounds_builder_collator_test.cpp b/src/mongo/db/query/index_bounds_builder_collator_test.cpp new file mode 100644 index 00000000000..9c697337740 --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_collator_test.cpp @@ -0,0 +1,682 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/index_bounds_builder_test.h" + +#include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/db/query/expression_index.h" + +namespace mongo { +namespace { + +TEST_F(IndexBoundsBuilderTest, TranslateExprEqualToStringRespectsCollation) { + BSONObj keyPattern = BSON("a" << 1); + BSONElement elt = keyPattern.firstElement(); + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(keyPattern); + testIndex.collator = &collator; + + BSONObj obj = BSON("a" << BSON("$_internalExprEq" + << "foo")); + auto expr = parseMatchExpression(obj); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualityToStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = BSON("a" + << "foo"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualityToNonStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = BSON("a" << 3); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 3, '': 3}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateNotEqualToStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = BSON("a" << BSON("$ne" + << "bar")); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + // Bounds should be [MinKey, "rab"), ("rab", MaxKey]. + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + + { + BSONObjBuilder bob; + bob.appendMinKey(""); + bob.append("", "rab"); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(bob.obj(), true, false))); + } + + { + BSONObjBuilder bob; + bob.append("", "rab"); + bob.appendMaxKey(""); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(bob.obj(), false, true))); + } +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualToStringElemMatchValueWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$elemMatch: {$eq: 'baz'}}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'zab', '': 'zab'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, TranslateLTEToStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lte: 'foo'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateLTEToNumberWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lte: 3}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': -Infinity, '': 3}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateLTStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lt: 'foo'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, false))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateLTNumberWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lt: 3}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': -Infinity, '': 3}"), true, false))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateGTStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gt: 'foo'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), false, false))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateGTNumberWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gt: 3}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 3, '': Infinity}"), false, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateGTEToStringWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gte: 'foo'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), true, false))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, TranslateGTEToNumberWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gte: 3}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 3, '': Infinity}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, SimplePrefixRegexWithMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: /^foo/}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, NotWithMockCollatorIsExact) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$ne: 3}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(minKeyIntObj(3), true, false))); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(maxKeyIntObj(3), false, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, ExistsTrueWithMockCollatorAndSparseIsExact) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + testIndex.sparse = true; + + BSONObj obj = fromjson("{a: {$exists: true}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValues())); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, ExistsFalseWithMockCollatorIsInexactFetch) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$exists: false}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': null, '': null}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, TypeStringIsInexactFetch) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$type: 'string'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, InWithStringAndCollatorIsExact) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$in: ['foo']}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, InWithNumberAndStringAndCollatorIsExact) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$in: [2, 'foo']}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 2, '': 2}"), true, true))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, InWithRegexAndCollatorIsInexactFetch) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$in: [/^foo/]}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, InWithNumberAndCollatorIsExact) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$in: [2]}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 2, '': 2}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, LTEMaxKeyWithCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lte: {$maxKey: 1}}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValues())); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, LTMaxKeyWithCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$lt: {$maxKey: 1}}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValuesRespectingInclusion( + BoundInclusion::kIncludeStartKeyOnly))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, GTEMinKeyWithCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gte: {$minKey: 1}}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValues())); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, GTMinKeyWithCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$gt: {$minKey: 1}}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValuesRespectingInclusion( + BoundInclusion::kIncludeEndKeyOnly))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, StringEqualityAgainstHashedIndexWithCollatorUsesHashOfCollationKey) { + BSONObj keyPattern = fromjson("{a: 'hashed'}"); + BSONElement elt = keyPattern.firstElement(); + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(keyPattern); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: 'foo'}"); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + BSONObj expectedCollationKey = BSON("" + << "oof"); + BSONObj expectedHash = ExpressionMapping::hash(expectedCollationKey.firstElement()); + BSONObjBuilder intervalBuilder; + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + BSONObj intervalObj = intervalBuilder.obj(); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(intervalObj, true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, EqualityToNumberAgainstHashedIndexWithCollatorUsesHash) { + BSONObj keyPattern = fromjson("{a: 'hashed'}"); + BSONElement elt = keyPattern.firstElement(); + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(keyPattern); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: 3}"); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + BSONObj expectedHash = ExpressionMapping::hash(obj.firstElement()); + BSONObjBuilder intervalBuilder; + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + BSONObj intervalObj = intervalBuilder.obj(); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(intervalObj, true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, InWithStringAgainstHashedIndexWithCollatorUsesHashOfCollationKey) { + BSONObj keyPattern = fromjson("{a: 'hashed'}"); + BSONElement elt = keyPattern.firstElement(); + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(keyPattern); + testIndex.collator = &collator; + + BSONObj obj = fromjson("{a: {$in: ['foo']}}"); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + BSONObj expectedCollationKey = BSON("" + << "oof"); + BSONObj expectedHash = ExpressionMapping::hash(expectedCollationKey.firstElement()); + BSONObjBuilder intervalBuilder; + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + intervalBuilder.append("", expectedHash.firstElement().numberLong()); + BSONObj intervalObj = intervalBuilder.obj(); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(intervalObj, true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/query/index_bounds_builder_eq_null_test.cpp b/src/mongo/db/query/index_bounds_builder_eq_null_test.cpp new file mode 100644 index 00000000000..bc6b09861cd --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_eq_null_test.cpp @@ -0,0 +1,431 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/index_bounds_builder_test.h" + +#include "mongo/db/query/expression_index.h" + +namespace mongo { +namespace { + +/** + * Asserts that 'oil' contains exactly two bounds: [[undefined, undefined], [null, null]]. + */ +void assertBoundsRepresentEqualsNull(const OrderedIntervalList& oil) { + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': undefined, '': undefined}"), true, true))); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': null, '': null}"), true, true))); +} + +TEST_F(IndexBoundsBuilderTest, TranslateExprEqualToNullIsInexactFetch) { + BSONObj keyPattern = BSON("a" << 1); + BSONElement elt = keyPattern.firstElement(); + auto testIndex = buildSimpleIndexEntry(keyPattern); + BSONObj obj = BSON("a" << BSON("$_internalExprEq" << BSONNULL)); + auto expr = parseMatchExpression(obj); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': undefined, '': undefined}"), true, true))); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': null, '': null}"), true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualsToNullShouldBuildInexactBounds) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + BSONObj obj = BSON("a" << BSONNULL); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, TranslateDottedEqualsToNullShouldBuildInexactBounds) { + BSONObj indexPattern = BSON("a.b" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + BSONObj obj = BSON("a.b" << BSONNULL); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a.b"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualsToNullMultiKeyShouldBuildInexactBounds) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikey = true; + + BSONObj obj = BSON("a" << BSONNULL); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, TranslateEqualsToNullShouldBuildTwoIntervalsForHashedIndex) { + BSONObj indexPattern = BSON("a" + << "hashed"); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.type = IndexType::INDEX_HASHED; + + BSONObj obj = BSON("a" << BSONNULL); + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + // We should have one for undefined, and one for null. + ASSERT_EQUALS(oil.intervals.size(), 2U); + { + const BSONObj undefinedElementObj = BSON("" << BSONUndefined); + const BSONObj hashedUndefinedInterval = + ExpressionMapping::hash(undefinedElementObj.firstElement()); + ASSERT_EQ(hashedUndefinedInterval.firstElement().type(), BSONType::NumberLong); + + const auto& firstInterval = oil.intervals[0]; + ASSERT_TRUE(firstInterval.startInclusive); + ASSERT_TRUE(firstInterval.endInclusive); + ASSERT_EQ(firstInterval.start.type(), BSONType::NumberLong); + ASSERT_EQ(firstInterval.start.numberLong(), + hashedUndefinedInterval.firstElement().numberLong()); + } + + { + const BSONObj nullElementObj = BSON("" << BSONNULL); + const BSONObj hashedNullInterval = ExpressionMapping::hash(nullElementObj.firstElement()); + ASSERT_EQ(hashedNullInterval.firstElement().type(), BSONType::NumberLong); + + const auto& secondInterval = oil.intervals[1]; + ASSERT_TRUE(secondInterval.startInclusive); + ASSERT_TRUE(secondInterval.endInclusive); + ASSERT_EQ(secondInterval.start.type(), BSONType::NumberLong); + ASSERT_EQ(secondInterval.start.numberLong(), + hashedNullInterval.firstElement().numberLong()); + } +} + +/** + * Asserts that 'oil' contains exactly two bounds: [MinKey, undefined) and (null, MaxKey]. + */ +void assertBoundsRepresentNotEqualsNull(const OrderedIntervalList& oil) { + ASSERT_EQUALS(oil.intervals.size(), 2U); + { + BSONObjBuilder bob; + bob.appendMinKey(""); + bob.appendUndefined(""); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(bob.obj(), true, false))); + } + + { + BSONObjBuilder bob; + bob.appendNull(""); + bob.appendMaxKey(""); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(bob.obj(), false, true))); + } +} + +const std::vector<BSONObj> kNeNullQueries = {BSON("a" << BSON("$ne" << BSONNULL)), + BSON("a" << BSON("$not" << BSON("$lte" << BSONNULL))), + BSON("a" << BSON("$not" << BSON("$gte" << BSONNULL)))}; + +TEST_F(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildExactBoundsIfIndexIsNotMultiKey) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + for (BSONObj obj : kNeNullQueries) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + // Bounds should be [MinKey, undefined), (null, MaxKey]. + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + assertBoundsRepresentNotEqualsNull(oil); + } +} + +TEST_F(IndexBoundsBuilderTest, + TranslateNotEqualToNullShouldBuildExactBoundsIfIndexIsNotMultiKeyOnRelevantPath) { + BSONObj indexPattern = BSON("a" << 1 << "b" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikeyPaths = {{}, {0}}; // "a" is not multi-key, but "b" is. + + for (BSONObj obj : kNeNullQueries) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + // Bounds should be [MinKey, undefined), (null, MaxKey]. + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + assertBoundsRepresentNotEqualsNull(oil); + } +} + +TEST_F(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildExactBoundsOnReverseIndex) { + BSONObj indexPattern = BSON("a" << -1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + for (BSONObj obj : kNeNullQueries) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + // Bounds should be [MinKey, undefined), (null, MaxKey]. + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + assertBoundsRepresentNotEqualsNull(oil); + } +} + +TEST_F(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikey = true; + + for (BSONObj obj : kNeNullQueries) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentNotEqualsNull(oil); + } +} + +TEST_F(IndexBoundsBuilderTest, TranslateInequalityToNullShouldProduceExactEmptyBounds) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + const std::vector<BSONObj> inequalities = {BSON("a" << BSON("$lt" << BSONNULL)), + BSON("a" << BSON("$gt" << BSONNULL))}; + + for (BSONObj obj : inequalities) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = parseMatchExpression(obj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + ASSERT(oil.intervals.empty()); + } +} + +TEST_F(IndexBoundsBuilderTest, TranslateNotInequalityToNullShouldProduceExactFullBounds) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + const std::vector<BSONObj> inequalities = { + BSON("a" << BSON("$not" << BSON("$lt" << BSONNULL))), + BSON("a" << BSON("$not" << BSON("$gt" << BSONNULL)))}; + + for (BSONObj obj : inequalities) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + ASSERT_EQ(oil.intervals.size(), 1); + ASSERT(oil.intervals.front().isMinToMax()); + } +} + +TEST_F(IndexBoundsBuilderTest, + TranslateNotInequalityToNullOnMultiKeyIndexShouldProduceInexactFullBounds) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikey = true; + + const std::vector<BSONObj> inequalities = { + BSON("a" << BSON("$not" << BSON("$lt" << BSONNULL))), + BSON("a" << BSON("$not" << BSON("$gt" << BSONNULL)))}; + + for (BSONObj obj : inequalities) { + // It's necessary to call optimize since the $not will have a singleton $and child, which + // IndexBoundsBuilder::translate cannot handle. + auto expr = MatchExpression::optimize(parseMatchExpression(obj)); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + ASSERT_EQ(oil.intervals.size(), 1); + ASSERT(oil.intervals.front().isMinToMax()); + } +} + +TEST_F(IndexBoundsBuilderTest, + TranslateDottedElemMatchValueNotEqualToNullShouldBuildExactBoundsIfIsMultiKeyOnThatPath) { + BSONObj indexPattern = BSON("a.b" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikeyPaths = {{1}}; // "a.b" is multikey. + + BSONObj matchObj = BSON("a.b" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); + auto expr = parseMatchExpression(matchObj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a.b"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentNotEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, + TranslateDottedFieldNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { + BSONObj indexPattern = BSON("a.b" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikey = true; + + BSONObj matchObj = BSON("a.b" << BSON("$ne" << BSONNULL)); + auto expr = parseMatchExpression(matchObj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a.b"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentNotEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, + TranslateElemMatchValueNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + testIndex.multikey = true; + + BSONObj obj = BSON("a" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentNotEqualsNull(oil); +} + +TEST_F(IndexBoundsBuilderTest, + TranslateElemMatchValueNotEqualToNullShouldBuildInExactBoundsIfIndexIsNotMultiKey) { + BSONObj indexPattern = BSON("a" << 1); + auto testIndex = buildSimpleIndexEntry(indexPattern); + + BSONObj matchObj = BSON("a" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); + auto expr = parseMatchExpression(matchObj); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate( + expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); + + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); + assertBoundsRepresentNotEqualsNull(oil); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/query/index_bounds_builder_interval_test.cpp b/src/mongo/db/query/index_bounds_builder_interval_test.cpp new file mode 100644 index 00000000000..3ccb72a91c2 --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_interval_test.cpp @@ -0,0 +1,231 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/index_bounds_builder_test.h" + +namespace mongo { +namespace { + +/** + * run isSingleInterval and return the result to calling test. + */ +bool testSingleInterval(IndexBounds bounds) { + BSONObj startKey; + bool startKeyIn; + BSONObj endKey; + bool endKeyIn; + return IndexBoundsBuilder::isSingleInterval(bounds, &startKey, &startKeyIn, &endKey, &endKeyIn); +} + +TEST(IndexBoundsBuilderTest, SingleFieldEqualityInterval) { + // Equality on a single field is a single interval. + OrderedIntervalList oil("a"); + IndexBounds bounds; + oil.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + bounds.fields.push_back(oil); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, SingleIntervalSingleFieldInterval) { + // Single interval on a single field is a single interval. + OrderedIntervalList oil("a"); + IndexBounds bounds; + oil.intervals.push_back(Interval(fromjson("{ '':5, '':Infinity }"), true, true)); + bounds.fields.push_back(oil); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, MultipleIntervalsSingleFieldInterval) { + // Multiple intervals on a single field is not a single interval. + OrderedIntervalList oil("a"); + IndexBounds bounds; + oil.intervals.push_back(Interval(fromjson("{ '':4, '':5 }"), true, true)); + oil.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + bounds.fields.push_back(oil); + ASSERT(!testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualityTwoFieldsInterval) { + // Equality on two fields is a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualityFirstFieldSingleIntervalSecondFieldInterval) { + // Equality on first field and single interval on second field + // is a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':6, '':Infinity }"), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, SingleIntervalFirstAndSecondFieldsInterval) { + // Single interval on first field and single interval on second field is + // not a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(fromjson("{ '':-Infinity, '':5 }"), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':6, '':Infinity }"), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + ASSERT(!testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, MultipleIntervalsTwoFieldsInterval) { + // Multiple intervals on two fields is not a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 4 << "" << 4), true, true)); + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(BSON("" << 7 << "" << 7), true, true)); + oil_b.intervals.push_back(Interval(BSON("" << 8 << "" << 8), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + ASSERT(!testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, MissingSecondFieldInterval) { + // when second field is not specified, still a compound single interval + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(IndexBoundsBuilder::allValues()); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualityTwoFieldsIntervalThirdInterval) { + // Equality on first two fields and single interval on third is a + // compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + OrderedIntervalList oil_c("c"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true)); + oil_c.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + bounds.fields.push_back(oil_c); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualitySingleIntervalMissingInterval) { + // Equality, then Single Interval, then missing is a compound single interval + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + OrderedIntervalList oil_c("c"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + bounds.fields.push_back(oil_c); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualitySingleMissingMissingInterval) { + // Equality, then single interval, then missing, then missing, + // is a compound single interval + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + OrderedIntervalList oil_c("c"); + OrderedIntervalList oil_d("d"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); + oil_d.intervals.push_back(IndexBoundsBuilder::allValues()); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + bounds.fields.push_back(oil_c); + bounds.fields.push_back(oil_d); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualitySingleMissingMissingMixedInterval) { + // Equality, then single interval, then missing, then missing, with mixed order + // fields is a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + OrderedIntervalList oil_c("c"); + OrderedIntervalList oil_d("d"); + IndexBounds bounds; + Interval allValues = IndexBoundsBuilder::allValues(); + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + oil_c.intervals.push_back(allValues); + IndexBoundsBuilder::reverseInterval(&allValues); + oil_d.intervals.push_back(allValues); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + bounds.fields.push_back(oil_c); + bounds.fields.push_back(oil_d); + ASSERT(testSingleInterval(bounds)); +} + +TEST(IndexBoundsBuilderTest, EqualitySingleMissingSingleInterval) { + // Equality, then single interval, then missing, then single interval is not + // a compound single interval. + OrderedIntervalList oil_a("a"); + OrderedIntervalList oil_b("b"); + OrderedIntervalList oil_c("c"); + OrderedIntervalList oil_d("d"); + IndexBounds bounds; + oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); + oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); + oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); + oil_d.intervals.push_back(Interval(fromjson("{ '':1, '':Infinity }"), true, true)); + bounds.fields.push_back(oil_a); + bounds.fields.push_back(oil_b); + bounds.fields.push_back(oil_c); + bounds.fields.push_back(oil_d); + ASSERT(!testSingleInterval(bounds)); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/query/index_bounds_builder_regex_test.cpp b/src/mongo/db/query/index_bounds_builder_regex_test.cpp new file mode 100644 index 00000000000..e112443e401 --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_regex_test.cpp @@ -0,0 +1,335 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/index_bounds_builder_test.h" + +#include "mongo/db/query/collation/collator_interface_mock.h" + +namespace mongo { +namespace { + +TEST_F(IndexBoundsBuilderTest, RootedLine) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedString) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedOptionalFirstChar) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^f?oo", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedOptionalSecondChar) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^fz?oo", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "f"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedMultiline) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "m", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedStringMultiline) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "m", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedCaseInsensitiveMulti) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "mi", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedComplex) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex( + "\\Af \t\vo\n\ro \\ \\# #comment", "mx", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo #"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteral) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^\\Qasdf\\E", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "asdf"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralWithExtra) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = + IndexBoundsBuilder::simpleRegex("^\\Qasdf\\E.*", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "asdf"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralNoEnd) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^\\Qasdf", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "asdf"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralBackslash) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = + IndexBoundsBuilder::simpleRegex("^\\Qasdf\\\\E", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "asdf\\"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralDotStar) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = + IndexBoundsBuilder::simpleRegex("^\\Qas.*df\\E", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "as.*df"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralNestedEscape) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = + IndexBoundsBuilder::simpleRegex("^\\Qas\\Q[df\\E", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "as\\Q[df"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, RootedLiteralNestedEscapeEnd) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = + IndexBoundsBuilder::simpleRegex("^\\Qas\\E\\\\E\\Q$df\\E", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "as\\E$df"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +// An anchored regular expression that uses the "|" operator is not considered "simple" and has +// non-tight index bounds. +TEST_F(IndexBoundsBuilderTest, PipeCharacterUsesInexactBounds) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^(a(a|$)|b", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, PipeCharacterUsesInexactBoundsWithTwoPrefixes) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^(a(a|$)|^b", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, PipeCharacterPrecededByEscapedBackslashUsesInexactBounds) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^a\\|b)", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); + + prefix = IndexBoundsBuilder::simpleRegex(R"(^(foo\\|bar)\\|baz)", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +// However, a regular expression with an escaped pipe (that is, using no special meaning) can use +// exact index bounds. +TEST_F(IndexBoundsBuilderTest, PipeCharacterEscapedWithBackslashUsesExactBounds) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^a\|b)", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "a|b"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); + + prefix = IndexBoundsBuilder::simpleRegex(R"(^\|1\|2\|\|)", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, "|1|2||"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, FalsePositiveOnPipeInQEEscapeSequenceUsesInexactBounds) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^\Q|\E)", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, FalsePositiveOnPipeInCharacterClassUsesInexactBounds) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^[|])", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +// SERVER-9035 +TEST_F(IndexBoundsBuilderTest, RootedSingleLineMode) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "s", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +// SERVER-9035 +TEST_F(IndexBoundsBuilderTest, NonRootedSingleLineMode) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("foo", "s", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +// SERVER-9035 +TEST_F(IndexBoundsBuilderTest, RootedComplexSingleLineMode) { + auto testIndex = buildSimpleIndexEntry(); + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex( + "\\Af \t\vo\n\ro \\ \\# #comment", "msx", testIndex, &tightness); + ASSERT_EQUALS(prefix, "foo #"); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, RootedRegexCantBeIndexedTightlyIfIndexHasCollation) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + auto testIndex = buildSimpleIndexEntry(); + testIndex.collator = &collator; + + IndexBoundsBuilder::BoundsTightness tightness; + std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "", testIndex, &tightness); + ASSERT_EQUALS(prefix, ""); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, SimpleNonPrefixRegex) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: /foo/}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': /foo/, '': /foo/}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, NonSimpleRegexWithPipe) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: /^foo.*|bar/}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[1].compare( + Interval(fromjson("{'': /^foo.*|bar/, '': /^foo.*|bar/}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED); +} + +TEST_F(IndexBoundsBuilderTest, SimpleRegexSingleLineMode) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: /^foo/s}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'foo', '': 'fop'}"), true, false))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': /^foo/s, '': /^foo/s}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::EXACT); +} + +TEST_F(IndexBoundsBuilderTest, SimplePrefixRegex) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: /^foo/}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.intervals.size(), 2U); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(fromjson("{'': 'foo', '': 'fop'}"), true, false))); + ASSERT_EQUALS( + Interval::INTERVAL_EQUALS, + oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); + ASSERT(tightness == IndexBoundsBuilder::EXACT); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp index 8aec96d37cf..077ccd7c386 100644 --- a/src/mongo/db/query/index_bounds_builder_test.cpp +++ b/src/mongo/db/query/index_bounds_builder_test.cpp @@ -29,14 +29,13 @@ #include "mongo/platform/basic.h" -#include "mongo/db/query/index_bounds_builder.h" +#include "mongo/db/query/index_bounds_builder_test.h" #include <limits> #include <memory> #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" -#include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/expression_index.h" #include "mongo/unittest/unittest.h" @@ -53,121 +52,12 @@ double negativeInfinity = -DoubleLimits::infinity(); double positiveInfinity = DoubleLimits::infinity(); double NaN = DoubleLimits::quiet_NaN(); -/** - * Make a minimal IndexEntry from just an optional key pattern. A dummy name will be added. An empty - * key pattern will be used if none is provided. - */ -IndexEntry buildSimpleIndexEntry(const BSONObj& kp = BSONObj()) { - return {kp, - IndexNames::nameToType(IndexNames::findPluginName(kp)), - false, - {}, - {}, - false, - false, - CoreIndexInfo::Identifier("test_foo"), - nullptr, - {}, - nullptr, - nullptr}; -} - -/** - * Utility function to create MatchExpression - */ -std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - StatusWithMatchExpression status = MatchExpressionParser::parse(obj, std::move(expCtx)); - ASSERT_TRUE(status.isOK()); - return std::unique_ptr<MatchExpression>(status.getValue().release()); -} - -/** - * Given a list of queries in 'toUnion', translate into index bounds and return - * the union of these bounds in the out-parameter 'oilOut'. - */ -void testTranslateAndUnion(const std::vector<BSONObj>& toUnion, - OrderedIntervalList* oilOut, - IndexBoundsBuilder::BoundsTightness* tightnessOut) { - auto testIndex = buildSimpleIndexEntry(); - - for (auto it = toUnion.begin(); it != toUnion.end(); ++it) { - auto expr = parseMatchExpression(*it); - BSONElement elt = it->firstElement(); - if (toUnion.begin() == it) { - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); - } else { - IndexBoundsBuilder::translateAndUnion(expr.get(), elt, testIndex, oilOut, tightnessOut); - } - } -} - -/** - * Given a list of queries in 'toUnion', translate into index bounds and return - * the intersection of these bounds in the out-parameter 'oilOut'. - */ -void testTranslateAndIntersect(const std::vector<BSONObj>& toIntersect, - OrderedIntervalList* oilOut, - IndexBoundsBuilder::BoundsTightness* tightnessOut) { - auto testIndex = buildSimpleIndexEntry(); - - for (auto it = toIntersect.begin(); it != toIntersect.end(); ++it) { - auto expr = parseMatchExpression(*it); - BSONElement elt = it->firstElement(); - if (toIntersect.begin() == it) { - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); - } else { - IndexBoundsBuilder::translateAndIntersect( - expr.get(), elt, testIndex, oilOut, tightnessOut); - } - } -} - -/** - * 'constraints' is a vector of BSONObj's representing match expressions, where - * each filter is paired with a boolean. If the boolean is true, then the filter's - * index bounds should be intersected with the other constraints; if false, then - * they should be unioned. The resulting bounds are returned in the - * out-parameter 'oilOut'. - */ -void testTranslate(const std::vector<std::pair<BSONObj, bool>>& constraints, - OrderedIntervalList* oilOut, - IndexBoundsBuilder::BoundsTightness* tightnessOut) { - auto testIndex = buildSimpleIndexEntry(); - - for (auto it = constraints.begin(); it != constraints.end(); ++it) { - BSONObj obj = it->first; - bool isIntersect = it->second; - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - if (constraints.begin() == it) { - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); - } else if (isIntersect) { - IndexBoundsBuilder::translateAndIntersect( - expr.get(), elt, testIndex, oilOut, tightnessOut); - } else { - IndexBoundsBuilder::translateAndUnion(expr.get(), elt, testIndex, oilOut, tightnessOut); - } - } -} - -/** - * run isSingleInterval and return the result to calling test. - */ -bool testSingleInterval(IndexBounds bounds) { - BSONObj startKey; - bool startKeyIn; - BSONObj endKey; - bool endKeyIn; - return IndexBoundsBuilder::isSingleInterval(bounds, &startKey, &startKeyIn, &endKey, &endKeyIn); -} - // // $elemMatch value // Example: {a: {$elemMatch: {$gt: 2}}} // -TEST(IndexBoundsBuilderTest, TranslateElemMatchValue) { +TEST_F(IndexBoundsBuilderTest, TranslateElemMatchValue) { auto testIndex = buildSimpleIndexEntry(); // Bounds generated should be the same as the embedded expression // except for the tightness. @@ -189,7 +79,7 @@ TEST(IndexBoundsBuilderTest, TranslateElemMatchValue) { // Comparison operators ($lte, $lt, $gt, $gte, $eq) // -TEST(IndexBoundsBuilderTest, TranslateLteNumber) { +TEST_F(IndexBoundsBuilderTest, TranslateLteNumber) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lte: 1}}"); auto expr = parseMatchExpression(obj); @@ -205,7 +95,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteNumber) { ASSERT(tightness == IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteNumberMin) { +TEST_F(IndexBoundsBuilderTest, TranslateLteNumberMin) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lte" << numberMin)); auto expr = parseMatchExpression(obj); @@ -221,7 +111,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteNumberMin) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteNegativeInfinity) { +TEST_F(IndexBoundsBuilderTest, TranslateLteNegativeInfinity) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lte: -Infinity}}"); auto expr = parseMatchExpression(obj); @@ -237,7 +127,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteNegativeInfinity) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteObject) { +TEST_F(IndexBoundsBuilderTest, TranslateLteObject) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lte: {b: 1}}}"); auto expr = parseMatchExpression(obj); @@ -252,7 +142,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteObject) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteCode) { +TEST_F(IndexBoundsBuilderTest, TranslateLteCode) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lte" << BSONCode("function(){ return 0; }"))); auto expr = parseMatchExpression(obj); @@ -268,7 +158,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteCode) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteCodeWScope) { +TEST_F(IndexBoundsBuilderTest, TranslateLteCodeWScope) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lte" << BSONCodeWScope("this.b == c", BSON("c" << 1)))); auto expr = parseMatchExpression(obj); @@ -285,7 +175,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteCodeWScope) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteMinKey) { +TEST_F(IndexBoundsBuilderTest, TranslateLteMinKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lte" << MINKEY)); auto expr = parseMatchExpression(obj); @@ -301,7 +191,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteMinKey) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteMaxKey) { +TEST_F(IndexBoundsBuilderTest, TranslateLteMaxKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lte" << MAXKEY)); auto expr = parseMatchExpression(obj); @@ -317,7 +207,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteMaxKey) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtNumber) { +TEST_F(IndexBoundsBuilderTest, TranslateLtNumber) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lt: 1}}"); auto expr = parseMatchExpression(obj); @@ -333,7 +223,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtNumber) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtNumberMin) { +TEST_F(IndexBoundsBuilderTest, TranslateLtNumberMin) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lt" << numberMin)); auto expr = parseMatchExpression(obj); @@ -349,7 +239,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtNumberMin) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtNegativeInfinity) { +TEST_F(IndexBoundsBuilderTest, TranslateLtNegativeInfinity) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lt: -Infinity}}"); auto expr = parseMatchExpression(obj); @@ -362,7 +252,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtNegativeInfinity) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtDate) { +TEST_F(IndexBoundsBuilderTest, TranslateLtDate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << LT << Date_t::fromMillisSinceEpoch(5000)); auto expr = parseMatchExpression(obj); @@ -378,7 +268,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtDate) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtObject) { +TEST_F(IndexBoundsBuilderTest, TranslateLtObject) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lt: {b: 1}}}"); auto expr = parseMatchExpression(obj); @@ -394,7 +284,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtObject) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtCode) { +TEST_F(IndexBoundsBuilderTest, TranslateLtCode) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lt" << BSONCode("function(){ return 0; }"))); auto expr = parseMatchExpression(obj); @@ -410,7 +300,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtCode) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtCodeWScope) { +TEST_F(IndexBoundsBuilderTest, TranslateLtCodeWScope) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lt" << BSONCodeWScope("this.b == c", BSON("c" << 1)))); auto expr = parseMatchExpression(obj); @@ -428,7 +318,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtCodeWScope) { } // Nothing can be less than MinKey so the resulting index bounds would be a useless empty range. -TEST(IndexBoundsBuilderTest, TranslateLtMinKeyDoesNotGenerateBounds) { +TEST_F(IndexBoundsBuilderTest, TranslateLtMinKeyDoesNotGenerateBounds) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lt" << MINKEY)); auto expr = parseMatchExpression(obj); @@ -441,7 +331,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtMinKeyDoesNotGenerateBounds) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtMaxKey) { +TEST_F(IndexBoundsBuilderTest, TranslateLtMaxKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$lt" << MAXKEY)); auto expr = parseMatchExpression(obj); @@ -457,7 +347,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtMaxKey) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtTimestamp) { +TEST_F(IndexBoundsBuilderTest, TranslateGtTimestamp) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << GT << Timestamp(2, 3)); auto expr = parseMatchExpression(obj); @@ -476,7 +366,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtTimestamp) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtNumber) { +TEST_F(IndexBoundsBuilderTest, TranslateGtNumber) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gt: 1}}"); auto expr = parseMatchExpression(obj); @@ -492,7 +382,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtNumber) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtNumberMax) { +TEST_F(IndexBoundsBuilderTest, TranslateGtNumberMax) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gt" << numberMax)); auto expr = parseMatchExpression(obj); @@ -508,7 +398,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtNumberMax) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtPositiveInfinity) { +TEST_F(IndexBoundsBuilderTest, TranslateGtPositiveInfinity) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gt: Infinity}}"); auto expr = parseMatchExpression(obj); @@ -521,7 +411,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtPositiveInfinity) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtString) { +TEST_F(IndexBoundsBuilderTest, TranslateGtString) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gt: 'abc'}}"); auto expr = parseMatchExpression(obj); @@ -537,7 +427,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtString) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtObject) { +TEST_F(IndexBoundsBuilderTest, TranslateGtObject) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gt: {b: 1}}}"); auto expr = parseMatchExpression(obj); @@ -553,7 +443,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtObject) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtCode) { +TEST_F(IndexBoundsBuilderTest, TranslateGtCode) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gt" << BSONCode("function(){ return 0; }"))); auto expr = parseMatchExpression(obj); @@ -569,7 +459,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtCode) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtCodeWScope) { +TEST_F(IndexBoundsBuilderTest, TranslateGtCodeWScope) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gt" << BSONCodeWScope("this.b == c", BSON("c" << 1)))); auto expr = parseMatchExpression(obj); @@ -585,7 +475,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtCodeWScope) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtMinKey) { +TEST_F(IndexBoundsBuilderTest, TranslateGtMinKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gt" << MINKEY)); auto expr = parseMatchExpression(obj); @@ -602,7 +492,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtMinKey) { } // Nothing can be greater than MaxKey so the resulting index bounds would be a useless empty range. -TEST(IndexBoundsBuilderTest, TranslateGtMaxKeyDoesNotGenerateBounds) { +TEST_F(IndexBoundsBuilderTest, TranslateGtMaxKeyDoesNotGenerateBounds) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gt" << MAXKEY)); auto expr = parseMatchExpression(obj); @@ -615,7 +505,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtMaxKeyDoesNotGenerateBounds) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteNumber) { +TEST_F(IndexBoundsBuilderTest, TranslateGteNumber) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gte: 1}}"); auto expr = parseMatchExpression(obj); @@ -631,7 +521,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteNumber) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteNumberMax) { +TEST_F(IndexBoundsBuilderTest, TranslateGteNumberMax) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gte" << numberMax)); auto expr = parseMatchExpression(obj); @@ -647,7 +537,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteNumberMax) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtePositiveInfinity) { +TEST_F(IndexBoundsBuilderTest, TranslateGtePositiveInfinity) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gte: Infinity}}"); auto expr = parseMatchExpression(obj); @@ -663,7 +553,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtePositiveInfinity) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteObject) { +TEST_F(IndexBoundsBuilderTest, TranslateGteObject) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gte: {b: 1}}}"); auto expr = parseMatchExpression(obj); @@ -679,7 +569,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteObject) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteCode) { +TEST_F(IndexBoundsBuilderTest, TranslateGteCode) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gte" << BSONCode("function(){ return 0; }"))); auto expr = parseMatchExpression(obj); @@ -695,7 +585,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteCode) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteCodeWScope) { +TEST_F(IndexBoundsBuilderTest, TranslateGteCodeWScope) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gte" << BSONCodeWScope("this.b == c", BSON("c" << 1)))); auto expr = parseMatchExpression(obj); @@ -711,7 +601,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteCodeWScope) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteMinKey) { +TEST_F(IndexBoundsBuilderTest, TranslateGteMinKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gte" << MINKEY)); auto expr = parseMatchExpression(obj); @@ -727,7 +617,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteMinKey) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteMaxKey) { +TEST_F(IndexBoundsBuilderTest, TranslateGteMaxKey) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$gte" << MAXKEY)); auto expr = parseMatchExpression(obj); @@ -743,7 +633,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteMaxKey) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateEqualNan) { +TEST_F(IndexBoundsBuilderTest, TranslateEqualNan) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: NaN}"); auto expr = parseMatchExpression(obj); @@ -758,7 +648,7 @@ TEST(IndexBoundsBuilderTest, TranslateEqualNan) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtNan) { +TEST_F(IndexBoundsBuilderTest, TranslateLtNan) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lt: NaN}}"); auto expr = parseMatchExpression(obj); @@ -771,7 +661,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtNan) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLteNan) { +TEST_F(IndexBoundsBuilderTest, TranslateLteNan) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$lte: NaN}}"); auto expr = parseMatchExpression(obj); @@ -786,7 +676,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteNan) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtNan) { +TEST_F(IndexBoundsBuilderTest, TranslateGtNan) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gt: NaN}}"); auto expr = parseMatchExpression(obj); @@ -799,7 +689,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtNan) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteNan) { +TEST_F(IndexBoundsBuilderTest, TranslateGteNan) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$gte: NaN}}"); auto expr = parseMatchExpression(obj); @@ -814,7 +704,7 @@ TEST(IndexBoundsBuilderTest, TranslateGteNan) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateEqual) { +TEST_F(IndexBoundsBuilderTest, TranslateEqual) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << 4); auto expr = parseMatchExpression(obj); @@ -829,7 +719,7 @@ TEST(IndexBoundsBuilderTest, TranslateEqual) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateExprEqual) { +TEST_F(IndexBoundsBuilderTest, TranslateExprEqual) { BSONObj keyPattern = BSON("a" << 1); BSONElement elt = keyPattern.firstElement(); auto testIndex = buildSimpleIndexEntry(keyPattern); @@ -845,7 +735,7 @@ TEST(IndexBoundsBuilderTest, TranslateExprEqual) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateExprEqualToStringRespectsCollation) { +TEST_F(IndexBoundsBuilderTest, TranslateExprEqualToStringRespectsCollation) { BSONObj keyPattern = BSON("a" << 1); BSONElement elt = keyPattern.firstElement(); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); @@ -866,7 +756,7 @@ TEST(IndexBoundsBuilderTest, TranslateExprEqualToStringRespectsCollation) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateExprEqualHashedIndex) { +TEST_F(IndexBoundsBuilderTest, TranslateExprEqualHashedIndex) { BSONObj keyPattern = fromjson("{a: 'hashed'}"); BSONElement elt = keyPattern.firstElement(); auto testIndex = buildSimpleIndexEntry(keyPattern); @@ -889,26 +779,7 @@ TEST(IndexBoundsBuilderTest, TranslateExprEqualHashedIndex) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, TranslateExprEqualToNullIsInexactFetch) { - BSONObj keyPattern = BSON("a" << 1); - BSONElement elt = keyPattern.firstElement(); - auto testIndex = buildSimpleIndexEntry(keyPattern); - BSONObj obj = BSON("a" << BSON("$_internalExprEq" << BSONNULL)); - auto expr = parseMatchExpression(obj); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': undefined, '': undefined}"), true, true))); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': null, '': null}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, TranslateArrayEqualBasic) { +TEST_F(IndexBoundsBuilderTest, TranslateArrayEqualBasic) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: [1, 2, 3]}"); auto expr = parseMatchExpression(obj); @@ -926,7 +797,7 @@ TEST(IndexBoundsBuilderTest, TranslateArrayEqualBasic) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, TranslateIn) { +TEST_F(IndexBoundsBuilderTest, TranslateIn) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$in: [8, 44, -1, -3]}}"); auto expr = parseMatchExpression(obj); @@ -947,7 +818,7 @@ TEST(IndexBoundsBuilderTest, TranslateIn) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateInArray) { +TEST_F(IndexBoundsBuilderTest, TranslateInArray) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$in: [[1], 2]}}"); auto expr = parseMatchExpression(obj); @@ -966,7 +837,7 @@ TEST(IndexBoundsBuilderTest, TranslateInArray) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, TranslateLteBinData) { +TEST_F(IndexBoundsBuilderTest, TranslateLteBinData) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson( "{a: {$lte: {$binary: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA'," @@ -987,7 +858,7 @@ TEST(IndexBoundsBuilderTest, TranslateLteBinData) { ASSERT_EQ(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateLtBinData) { +TEST_F(IndexBoundsBuilderTest, TranslateLtBinData) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson( "{a: {$lt: {$binary: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA'," @@ -1008,7 +879,7 @@ TEST(IndexBoundsBuilderTest, TranslateLtBinData) { ASSERT_EQ(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGtBinData) { +TEST_F(IndexBoundsBuilderTest, TranslateGtBinData) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson( "{a: {$gt: {$binary: '////////////////////////////'," @@ -1029,7 +900,7 @@ TEST(IndexBoundsBuilderTest, TranslateGtBinData) { ASSERT_EQ(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, TranslateGteBinData) { +TEST_F(IndexBoundsBuilderTest, TranslateGteBinData) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson( "{a: {$gte: {$binary: '////////////////////////////'," @@ -1051,37 +922,10 @@ TEST(IndexBoundsBuilderTest, TranslateGteBinData) { } // -// $type -// - -TEST(IndexBoundsBuilderTest, TypeNumber) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: {$type: 'number'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - - // Build the expected interval. - BSONObjBuilder bob; - BSONType type = BSONType::NumberInt; - bob.appendMinForType("", type); - bob.appendMaxForType("", type); - BSONObj expectedInterval = bob.obj(); - - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(expectedInterval, true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -// // $exists tests // -TEST(IndexBoundsBuilderTest, ExistsTrue) { +TEST_F(IndexBoundsBuilderTest, ExistsTrue) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$exists: true}}"); auto expr = parseMatchExpression(obj); @@ -1096,7 +940,7 @@ TEST(IndexBoundsBuilderTest, ExistsTrue) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, ExistsFalse) { +TEST_F(IndexBoundsBuilderTest, ExistsFalse) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$exists: false}}"); auto expr = parseMatchExpression(obj); @@ -1111,7 +955,7 @@ TEST(IndexBoundsBuilderTest, ExistsFalse) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, ExistsTrueSparse) { +TEST_F(IndexBoundsBuilderTest, ExistsTrueSparse) { auto keyPattern = BSONObj(); IndexEntry testIndex = IndexEntry(keyPattern, @@ -1143,7 +987,7 @@ TEST(IndexBoundsBuilderTest, ExistsTrueSparse) { // Union tests // -TEST(IndexBoundsBuilderTest, UnionTwoLt) { +TEST_F(IndexBoundsBuilderTest, UnionTwoLt) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: {$lt: 1}}")); @@ -1159,7 +1003,7 @@ TEST(IndexBoundsBuilderTest, UnionTwoLt) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, UnionDupEq) { +TEST_F(IndexBoundsBuilderTest, UnionDupEq) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: 1}")); @@ -1177,7 +1021,7 @@ TEST(IndexBoundsBuilderTest, UnionDupEq) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, UnionGtLt) { +TEST_F(IndexBoundsBuilderTest, UnionGtLt) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toUnion; toUnion.push_back(fromjson("{a: {$gt: 1}}")); @@ -1193,7 +1037,7 @@ TEST(IndexBoundsBuilderTest, UnionGtLt) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, UnionTwoEmptyRanges) { +TEST_F(IndexBoundsBuilderTest, UnionTwoEmptyRanges) { auto testIndex = buildSimpleIndexEntry(); std::vector<std::pair<BSONObj, bool>> constraints; constraints.push_back(std::make_pair(fromjson("{a: {$gt: 1}}"), true)); @@ -1210,7 +1054,7 @@ TEST(IndexBoundsBuilderTest, UnionTwoEmptyRanges) { // Intersection tests // -TEST(IndexBoundsBuilderTest, IntersectTwoLt) { +TEST_F(IndexBoundsBuilderTest, IntersectTwoLt) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$lt: 1}}")); @@ -1226,7 +1070,7 @@ TEST(IndexBoundsBuilderTest, IntersectTwoLt) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectEqGte) { +TEST_F(IndexBoundsBuilderTest, IntersectEqGte) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: 1}}")); @@ -1241,7 +1085,7 @@ TEST(IndexBoundsBuilderTest, IntersectEqGte) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectGtLte) { +TEST_F(IndexBoundsBuilderTest, IntersectGtLte) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 0}}")); @@ -1256,7 +1100,7 @@ TEST(IndexBoundsBuilderTest, IntersectGtLte) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectGtIn) { +TEST_F(IndexBoundsBuilderTest, IntersectGtIn) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 4}}")); @@ -1273,7 +1117,7 @@ TEST(IndexBoundsBuilderTest, IntersectGtIn) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectionIsPointInterval) { +TEST_F(IndexBoundsBuilderTest, IntersectionIsPointInterval) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gte: 1}}")); @@ -1288,7 +1132,7 @@ TEST(IndexBoundsBuilderTest, IntersectionIsPointInterval) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectFullyContained) { +TEST_F(IndexBoundsBuilderTest, IntersectFullyContained) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 5}}")); @@ -1305,7 +1149,7 @@ TEST(IndexBoundsBuilderTest, IntersectFullyContained) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, EmptyIntersection) { +TEST_F(IndexBoundsBuilderTest, EmptyIntersection) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: 1}}")); @@ -1321,7 +1165,7 @@ TEST(IndexBoundsBuilderTest, EmptyIntersection) { // $mod // -TEST(IndexBoundsBuilderTest, TranslateMod) { +TEST_F(IndexBoundsBuilderTest, TranslateMod) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$mod: [2, 0]}}"); auto expr = parseMatchExpression(obj); @@ -1338,522 +1182,11 @@ TEST(IndexBoundsBuilderTest, TranslateMod) { } // -// Test simpleRegex -// - -TEST(SimpleRegexTest, RootedLine) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedString) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedOptionalFirstChar) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^f?oo", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedOptionalSecondChar) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^fz?oo", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "f"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedMultiline) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "m", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedStringMultiline) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "m", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedCaseInsensitiveMulti) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("\\Afoo", "mi", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedComplex) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex( - "\\Af \t\vo\n\ro \\ \\# #comment", "mx", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo #"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedLiteral) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^\\Qasdf\\E", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "asdf"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedLiteralWithExtra) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = - IndexBoundsBuilder::simpleRegex("^\\Qasdf\\E.*", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "asdf"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedLiteralNoEnd) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^\\Qasdf", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "asdf"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedLiteralBackslash) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = - IndexBoundsBuilder::simpleRegex("^\\Qasdf\\\\E", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "asdf\\"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedLiteralDotStar) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = - IndexBoundsBuilder::simpleRegex("^\\Qas.*df\\E", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "as.*df"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedLiteralNestedEscape) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = - IndexBoundsBuilder::simpleRegex("^\\Qas\\Q[df\\E", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "as\\Q[df"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, RootedLiteralNestedEscapeEnd) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = - IndexBoundsBuilder::simpleRegex("^\\Qas\\E\\\\E\\Q$df\\E", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "as\\E$df"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -// An anchored regular expression that uses the "|" operator is not considered "simple" and has -// non-tight index bounds. -TEST(SimpleRegexTest, PipeCharacterUsesInexactBounds) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^(a(a|$)|b", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, PipeCharacterUsesInexactBoundsWithTwoPrefixes) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^(a(a|$)|^b", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, PipeCharacterPrecededByEscapedBackslashUsesInexactBounds) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^a\\|b)", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); - - prefix = IndexBoundsBuilder::simpleRegex(R"(^(foo\\|bar)\\|baz)", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -// However, a regular expression with an escaped pipe (that is, using no special meaning) can use -// exact index bounds. -TEST(SimpleRegexTest, PipeCharacterEscapedWithBackslashUsesExactBounds) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^a\|b)", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "a|b"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - - prefix = IndexBoundsBuilder::simpleRegex(R"(^\|1\|2\|\|)", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, "|1|2||"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(SimpleRegexTest, FalsePositiveOnPipeInQEEscapeSequenceUsesInexactBounds) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^\Q|\E)", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, FalsePositiveOnPipeInCharacterClassUsesInexactBounds) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex(R"(^[|])", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -// SERVER-9035 -TEST(SimpleRegexTest, RootedSingleLineMode) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "s", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -// SERVER-9035 -TEST(SimpleRegexTest, NonRootedSingleLineMode) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("foo", "s", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -// SERVER-9035 -TEST(SimpleRegexTest, RootedComplexSingleLineMode) { - auto testIndex = buildSimpleIndexEntry(); - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex( - "\\Af \t\vo\n\ro \\ \\# #comment", "msx", testIndex, &tightness); - ASSERT_EQUALS(prefix, "foo #"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(SimpleRegexTest, RootedRegexCantBeIndexedTightlyIfIndexHasCollation) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - IndexBoundsBuilder::BoundsTightness tightness; - std::string prefix = IndexBoundsBuilder::simpleRegex("^foo", "", testIndex, &tightness); - ASSERT_EQUALS(prefix, ""); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -// -// Regex bounds -// - -TEST(IndexBoundsBuilderTest, SimpleNonPrefixRegex) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: /foo/}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': /foo/, '': /foo/}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(IndexBoundsBuilderTest, NonSimpleRegexWithPipe) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: /^foo.*|bar/}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare( - Interval(fromjson("{'': /^foo.*|bar/, '': /^foo.*|bar/}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED); -} - -TEST(IndexBoundsBuilderTest, SimpleRegexSingleLineMode) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: /^foo/s}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'foo', '': 'fop'}"), true, false))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': /^foo/s, '': /^foo/s}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, SimplePrefixRegex) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: /^foo/}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'foo', '': 'fop'}"), true, false))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::EXACT); -} - -// -// isSingleInterval -// - -TEST(IndexBoundsBuilderTest, SingleFieldEqualityInterval) { - // Equality on a single field is a single interval. - OrderedIntervalList oil("a"); - IndexBounds bounds; - oil.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - bounds.fields.push_back(oil); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, SingleIntervalSingleFieldInterval) { - // Single interval on a single field is a single interval. - OrderedIntervalList oil("a"); - IndexBounds bounds; - oil.intervals.push_back(Interval(fromjson("{ '':5, '':Infinity }"), true, true)); - bounds.fields.push_back(oil); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, MultipleIntervalsSingleFieldInterval) { - // Multiple intervals on a single field is not a single interval. - OrderedIntervalList oil("a"); - IndexBounds bounds; - oil.intervals.push_back(Interval(fromjson("{ '':4, '':5 }"), true, true)); - oil.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - bounds.fields.push_back(oil); - ASSERT(!testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualityTwoFieldsInterval) { - // Equality on two fields is a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualityFirstFieldSingleIntervalSecondFieldInterval) { - // Equality on first field and single interval on second field - // is a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':6, '':Infinity }"), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, SingleIntervalFirstAndSecondFieldsInterval) { - // Single interval on first field and single interval on second field is - // not a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(fromjson("{ '':-Infinity, '':5 }"), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':6, '':Infinity }"), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - ASSERT(!testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, MultipleIntervalsTwoFieldsInterval) { - // Multiple intervals on two fields is not a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 4 << "" << 4), true, true)); - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(BSON("" << 7 << "" << 7), true, true)); - oil_b.intervals.push_back(Interval(BSON("" << 8 << "" << 8), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - ASSERT(!testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, MissingSecondFieldInterval) { - // when second field is not specified, still a compound single interval - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(IndexBoundsBuilder::allValues()); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualityTwoFieldsIntervalThirdInterval) { - // Equality on first two fields and single interval on third is a - // compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - OrderedIntervalList oil_c("c"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(BSON("" << 6 << "" << 6), true, true)); - oil_c.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - bounds.fields.push_back(oil_c); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualitySingleIntervalMissingInterval) { - // Equality, then Single Interval, then missing is a compound single interval - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - OrderedIntervalList oil_c("c"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - bounds.fields.push_back(oil_c); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualitySingleMissingMissingInterval) { - // Equality, then single interval, then missing, then missing, - // is a compound single interval - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - OrderedIntervalList oil_c("c"); - OrderedIntervalList oil_d("d"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); - oil_d.intervals.push_back(IndexBoundsBuilder::allValues()); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - bounds.fields.push_back(oil_c); - bounds.fields.push_back(oil_d); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualitySingleMissingMissingMixedInterval) { - // Equality, then single interval, then missing, then missing, with mixed order - // fields is a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - OrderedIntervalList oil_c("c"); - OrderedIntervalList oil_d("d"); - IndexBounds bounds; - Interval allValues = IndexBoundsBuilder::allValues(); - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - oil_c.intervals.push_back(allValues); - IndexBoundsBuilder::reverseInterval(&allValues); - oil_d.intervals.push_back(allValues); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - bounds.fields.push_back(oil_c); - bounds.fields.push_back(oil_d); - ASSERT(testSingleInterval(bounds)); -} - -TEST(IndexBoundsBuilderTest, EqualitySingleMissingSingleInterval) { - // Equality, then single interval, then missing, then single interval is not - // a compound single interval. - OrderedIntervalList oil_a("a"); - OrderedIntervalList oil_b("b"); - OrderedIntervalList oil_c("c"); - OrderedIntervalList oil_d("d"); - IndexBounds bounds; - oil_a.intervals.push_back(Interval(BSON("" << 5 << "" << 5), true, true)); - oil_b.intervals.push_back(Interval(fromjson("{ '':7, '':Infinity }"), true, true)); - oil_c.intervals.push_back(IndexBoundsBuilder::allValues()); - oil_d.intervals.push_back(Interval(fromjson("{ '':1, '':Infinity }"), true, true)); - bounds.fields.push_back(oil_a); - bounds.fields.push_back(oil_b); - bounds.fields.push_back(oil_c); - bounds.fields.push_back(oil_d); - ASSERT(!testSingleInterval(bounds)); -} - -// // Complementing bounds for negations // -/** - * Get a BSONObj which represents the interval from - * MinKey to 'end'. - */ -BSONObj minKeyIntObj(int end) { - BSONObjBuilder bob; - bob.appendMinKey(""); - bob.appendNumber("", end); - return bob.obj(); -} - -/** - * Get a BSONObj which represents the interval from - * 'start' to MaxKey. - */ -BSONObj maxKeyIntObj(int start) { - BSONObjBuilder bob; - bob.appendNumber("", start); - bob.appendMaxKey(""); - return bob.obj(); -} - // Expected oil: [MinKey, 3), (3, MaxKey] -TEST(IndexBoundsBuilderTest, SimpleNE) { +TEST_F(IndexBoundsBuilderTest, SimpleNE) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = BSON("a" << BSON("$ne" << 3)); auto expr = parseMatchExpression(obj); @@ -1870,7 +1203,7 @@ TEST(IndexBoundsBuilderTest, SimpleNE) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, IntersectWithNE) { +TEST_F(IndexBoundsBuilderTest, IntersectWithNE) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toIntersect; toIntersect.push_back(fromjson("{a: {$gt: 1}}")); @@ -1888,7 +1221,7 @@ TEST(IndexBoundsBuilderTest, IntersectWithNE) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -TEST(IndexBoundsBuilderTest, UnionizeWithNE) { +TEST_F(IndexBoundsBuilderTest, UnionizeWithNE) { auto testIndex = buildSimpleIndexEntry(); std::vector<BSONObj> toUnionize; toUnionize.push_back(fromjson("{a: {$ne: 3}}")); @@ -1903,1096 +1236,7 @@ TEST(IndexBoundsBuilderTest, UnionizeWithNE) { ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); } -// Test $type bounds for Code BSON type. -TEST(IndexBoundsBuilderTest, CodeTypeBounds) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: {$type: 13}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - // Build the expected interval. - BSONObjBuilder bob; - bob.appendCode("", ""); - bob.appendCodeWScope("", "", BSONObj()); - BSONObj expectedInterval = bob.obj(); - - // Check the output of translate(). - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(expectedInterval, true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -// Test $type bounds for Code With Scoped BSON type. -TEST(IndexBoundsBuilderTest, CodeWithScopeTypeBounds) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: {$type: 15}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - // Build the expected interval. - BSONObjBuilder bob; - bob.appendCodeWScope("", "", BSONObj()); - bob.appendMaxKey(""); - BSONObj expectedInterval = bob.obj(); - - // Check the output of translate(). - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(expectedInterval, true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -// Test $type bounds for double BSON type. -TEST(IndexBoundsBuilderTest, DoubleTypeBounds) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: {$type: 1}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - // Build the expected interval. - BSONObjBuilder bob; - bob.appendNumber("", NaN); - bob.appendNumber("", positiveInfinity); - BSONObj expectedInterval = bob.obj(); - - // Check the output of translate(). - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(expectedInterval, true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, TypeArrayBounds) { - auto testIndex = buildSimpleIndexEntry(); - BSONObj obj = fromjson("{a: {$type: 'array'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - // Check the output of translate(). - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValues())); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -// -// Collation-related tests. -// - -TEST(IndexBoundsBuilderTest, TranslateEqualityToStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = BSON("a" - << "foo"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateEqualityToNonStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = BSON("a" << 3); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 3, '': 3}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -/** - * Asserts that 'oil' contains exactly two bounds: [[undefined, undefined], [null, null]]. - */ -void assertBoundsRepresentEqualsNull(const OrderedIntervalList& oil) { - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': undefined, '': undefined}"), true, true))); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': null, '': null}"), true, true))); -} - -TEST(IndexBoundsBuilderTest, TranslateEqualsToNullShouldBuildInexactBounds) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - BSONObj obj = BSON("a" << BSONNULL); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, TranslateDottedEqualsToNullShouldBuildInexactBounds) { - BSONObj indexPattern = BSON("a.b" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - BSONObj obj = BSON("a.b" << BSONNULL); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a.b"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, TranslateEqualsToNullMultiKeyShouldBuildInexactBounds) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikey = true; - - BSONObj obj = BSON("a" << BSONNULL); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, TranslateEqualsToNullShouldBuildTwoIntervalsForHashedIndex) { - BSONObj indexPattern = BSON("a" - << "hashed"); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.type = IndexType::INDEX_HASHED; - - BSONObj obj = BSON("a" << BSONNULL); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - // We should have one for undefined, and one for null. - ASSERT_EQUALS(oil.intervals.size(), 2U); - { - const BSONObj undefinedElementObj = BSON("" << BSONUndefined); - const BSONObj hashedUndefinedInterval = - ExpressionMapping::hash(undefinedElementObj.firstElement()); - ASSERT_EQ(hashedUndefinedInterval.firstElement().type(), BSONType::NumberLong); - - const auto& firstInterval = oil.intervals[0]; - ASSERT_TRUE(firstInterval.startInclusive); - ASSERT_TRUE(firstInterval.endInclusive); - ASSERT_EQ(firstInterval.start.type(), BSONType::NumberLong); - ASSERT_EQ(firstInterval.start.numberLong(), - hashedUndefinedInterval.firstElement().numberLong()); - } - - { - const BSONObj nullElementObj = BSON("" << BSONNULL); - const BSONObj hashedNullInterval = ExpressionMapping::hash(nullElementObj.firstElement()); - ASSERT_EQ(hashedNullInterval.firstElement().type(), BSONType::NumberLong); - - const auto& secondInterval = oil.intervals[1]; - ASSERT_TRUE(secondInterval.startInclusive); - ASSERT_TRUE(secondInterval.endInclusive); - ASSERT_EQ(secondInterval.start.type(), BSONType::NumberLong); - ASSERT_EQ(secondInterval.start.numberLong(), - hashedNullInterval.firstElement().numberLong()); - } -} - -/** - * Asserts that 'oil' contains exactly two bounds: [MinKey, undefined) and (null, MaxKey]. - */ -void assertBoundsRepresentNotEqualsNull(const OrderedIntervalList& oil) { - ASSERT_EQUALS(oil.intervals.size(), 2U); - { - BSONObjBuilder bob; - bob.appendMinKey(""); - bob.appendUndefined(""); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(bob.obj(), true, false))); - } - - { - BSONObjBuilder bob; - bob.appendNull(""); - bob.appendMaxKey(""); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(bob.obj(), false, true))); - } -} - -const std::vector<BSONObj> kNeNullQueries = {BSON("a" << BSON("$ne" << BSONNULL)), - BSON("a" << BSON("$not" << BSON("$lte" << BSONNULL))), - BSON("a" << BSON("$not" << BSON("$gte" << BSONNULL)))}; - -TEST(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildExactBoundsIfIndexIsNotMultiKey) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - for (BSONObj obj : kNeNullQueries) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - // Bounds should be [MinKey, undefined), (null, MaxKey]. - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - assertBoundsRepresentNotEqualsNull(oil); - } -} - -TEST(IndexBoundsBuilderTest, - TranslateNotEqualToNullShouldBuildExactBoundsIfIndexIsNotMultiKeyOnRelevantPath) { - BSONObj indexPattern = BSON("a" << 1 << "b" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikeyPaths = {{}, {0}}; // "a" is not multi-key, but "b" is. - - for (BSONObj obj : kNeNullQueries) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - // Bounds should be [MinKey, undefined), (null, MaxKey]. - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - assertBoundsRepresentNotEqualsNull(oil); - } -} - -TEST(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildExactBoundsOnReverseIndex) { - BSONObj indexPattern = BSON("a" << -1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - for (BSONObj obj : kNeNullQueries) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - // Bounds should be [MinKey, undefined), (null, MaxKey]. - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - assertBoundsRepresentNotEqualsNull(oil); - } -} - -TEST(IndexBoundsBuilderTest, TranslateNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikey = true; - - for (BSONObj obj : kNeNullQueries) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentNotEqualsNull(oil); - } -} - -TEST(IndexBoundsBuilderTest, TranslateInequalityToNullShouldProduceExactEmptyBounds) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - const std::vector<BSONObj> inequalities = {BSON("a" << BSON("$lt" << BSONNULL)), - BSON("a" << BSON("$gt" << BSONNULL))}; - - for (BSONObj obj : inequalities) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - ASSERT(oil.intervals.empty()); - } -} - -TEST(IndexBoundsBuilderTest, TranslateNotInequalityToNullShouldProduceExactFullBounds) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - const std::vector<BSONObj> inequalities = { - BSON("a" << BSON("$not" << BSON("$lt" << BSONNULL))), - BSON("a" << BSON("$not" << BSON("$gt" << BSONNULL)))}; - - for (BSONObj obj : inequalities) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - ASSERT_EQ(oil.intervals.size(), 1); - ASSERT(oil.intervals.front().isMinToMax()); - } -} - -TEST(IndexBoundsBuilderTest, - TranslateNotInequalityToNullOnMultiKeyIndexShouldProduceInexactFullBounds) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikey = true; - - const std::vector<BSONObj> inequalities = { - BSON("a" << BSON("$not" << BSON("$lt" << BSONNULL))), - BSON("a" << BSON("$not" << BSON("$gt" << BSONNULL)))}; - - for (BSONObj obj : inequalities) { - // It's necessary to call optimize since the $not will have a singleton $and child, which - // IndexBoundsBuilder::translate cannot handle. - auto expr = MatchExpression::optimize(parseMatchExpression(obj)); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - ASSERT_EQ(oil.intervals.size(), 1); - ASSERT(oil.intervals.front().isMinToMax()); - } -} - -TEST(IndexBoundsBuilderTest, - TranslateDottedElemMatchValueNotEqualToNullShouldBuildExactBoundsIfIsMultiKeyOnThatPath) { - BSONObj indexPattern = BSON("a.b" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikeyPaths = {{1}}; // "a.b" is multikey. - - BSONObj matchObj = BSON("a.b" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); - auto expr = parseMatchExpression(matchObj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a.b"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentNotEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, - TranslateDottedFieldNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { - BSONObj indexPattern = BSON("a.b" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikey = true; - - BSONObj matchObj = BSON("a.b" << BSON("$ne" << BSONNULL)); - auto expr = parseMatchExpression(matchObj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a.b"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentNotEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, - TranslateElemMatchValueNotEqualToNullShouldBuildInexactBoundsIfIndexIsMultiKey) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - testIndex.multikey = true; - - BSONObj obj = BSON("a" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentNotEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, - TranslateElemMatchValueNotEqualToNullShouldBuildInExactBoundsIfIndexIsNotMultiKey) { - BSONObj indexPattern = BSON("a" << 1); - auto testIndex = buildSimpleIndexEntry(indexPattern); - - BSONObj matchObj = BSON("a" << BSON("$elemMatch" << BSON("$ne" << BSONNULL))); - auto expr = parseMatchExpression(matchObj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate( - expr.get(), indexPattern.firstElement(), testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); - assertBoundsRepresentNotEqualsNull(oil); -} - -TEST(IndexBoundsBuilderTest, TranslateNotEqualToStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = BSON("a" << BSON("$ne" - << "bar")); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - // Bounds should be [MinKey, "rab"), ("rab", MaxKey]. - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); - - { - BSONObjBuilder bob; - bob.appendMinKey(""); - bob.append("", "rab"); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(bob.obj(), true, false))); - } - - { - BSONObjBuilder bob; - bob.append("", "rab"); - bob.appendMaxKey(""); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(bob.obj(), false, true))); - } -} - -TEST(IndexBoundsBuilderTest, TranslateEqualToStringElemMatchValueWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$elemMatch: {$eq: 'baz'}}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'zab', '': 'zab'}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, TranslateLTEToStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lte: 'foo'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateLTEToNumberWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lte: 3}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': -Infinity, '': 3}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateLTStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lt: 'foo'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': 'oof'}"), true, false))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateLTNumberWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lt: 3}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': -Infinity, '': 3}"), true, false))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateGTStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gt: 'foo'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), false, false))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateGTNumberWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gt: 3}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 3, '': Infinity}"), false, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateGTEToStringWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gte: 'foo'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': {}}"), true, false))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, TranslateGTEToNumberWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gte: 3}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 3, '': Infinity}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, SimplePrefixRegexWithMockCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: /^foo/}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, NotWithMockCollatorIsExact) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$ne: 3}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(minKeyIntObj(3), true, false))); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(maxKeyIntObj(3), false, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, ExistsTrueWithMockCollatorAndSparseIsExact) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - testIndex.sparse = true; - - BSONObj obj = fromjson("{a: {$exists: true}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValues())); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, ExistsFalseWithMockCollatorIsInexactFetch) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$exists: false}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': null, '': null}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, TypeStringIsInexactFetch) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$type: 'string'}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, InWithStringAndCollatorIsExact) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$in: ['foo']}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, InWithNumberAndStringAndCollatorIsExact) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$in: [2, 'foo']}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 2, '': 2}"), true, true))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': 'oof', '': 'oof'}"), true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, InWithRegexAndCollatorIsInexactFetch) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$in: [/^foo/]}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.intervals.size(), 2U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false))); - ASSERT_EQUALS( - Interval::INTERVAL_EQUALS, - oil.intervals[1].compare(Interval(fromjson("{'': /^foo/, '': /^foo/}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, InWithNumberAndCollatorIsExact) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$in: [2]}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(fromjson("{'': 2, '': 2}"), true, true))); - ASSERT(tightness == IndexBoundsBuilder::EXACT); -} - -TEST(IndexBoundsBuilderTest, LTEMaxKeyWithCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lte: {$maxKey: 1}}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValues())); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, LTMaxKeyWithCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$lt: {$maxKey: 1}}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValuesRespectingInclusion( - BoundInclusion::kIncludeStartKeyOnly))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, GTEMinKeyWithCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gte: {$minKey: 1}}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValues())); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, GTMinKeyWithCollator) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$gt: {$minKey: 1}}}"); - auto expr = parseMatchExpression(obj); - BSONElement elt = obj.firstElement(); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(IndexBoundsBuilder::allValuesRespectingInclusion( - BoundInclusion::kIncludeEndKeyOnly))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, StringEqualityAgainstHashedIndexWithCollatorUsesHashOfCollationKey) { - BSONObj keyPattern = fromjson("{a: 'hashed'}"); - BSONElement elt = keyPattern.firstElement(); - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(keyPattern); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: 'foo'}"); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - BSONObj expectedCollationKey = BSON("" - << "oof"); - BSONObj expectedHash = ExpressionMapping::hash(expectedCollationKey.firstElement()); - BSONObjBuilder intervalBuilder; - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - BSONObj intervalObj = intervalBuilder.obj(); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(intervalObj, true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, EqualityToNumberAgainstHashedIndexWithCollatorUsesHash) { - BSONObj keyPattern = fromjson("{a: 'hashed'}"); - BSONElement elt = keyPattern.firstElement(); - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(keyPattern); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: 3}"); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - BSONObj expectedHash = ExpressionMapping::hash(obj.firstElement()); - BSONObjBuilder intervalBuilder; - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - BSONObj intervalObj = intervalBuilder.obj(); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(intervalObj, true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, InWithStringAgainstHashedIndexWithCollatorUsesHashOfCollationKey) { - BSONObj keyPattern = fromjson("{a: 'hashed'}"); - BSONElement elt = keyPattern.firstElement(); - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - auto testIndex = buildSimpleIndexEntry(keyPattern); - testIndex.collator = &collator; - - BSONObj obj = fromjson("{a: {$in: ['foo']}}"); - auto expr = parseMatchExpression(obj); - - OrderedIntervalList oil; - IndexBoundsBuilder::BoundsTightness tightness; - IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); - - BSONObj expectedCollationKey = BSON("" - << "oof"); - BSONObj expectedHash = ExpressionMapping::hash(expectedCollationKey.firstElement()); - BSONObjBuilder intervalBuilder; - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - intervalBuilder.append("", expectedHash.firstElement().numberLong()); - BSONObj intervalObj = intervalBuilder.obj(); - - ASSERT_EQUALS(oil.name, "a"); - ASSERT_EQUALS(oil.intervals.size(), 1U); - ASSERT_EQUALS(Interval::INTERVAL_EQUALS, - oil.intervals[0].compare(Interval(intervalObj, true, true))); - ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH); -} - -TEST(IndexBoundsBuilderTest, TypeArrayWithAdditionalTypesHasOpenBounds) { +TEST_F(IndexBoundsBuilderTest, TypeArrayWithAdditionalTypesHasOpenBounds) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$type: ['array', 'long']}}"); auto expr = parseMatchExpression(obj); @@ -3009,7 +1253,7 @@ TEST(IndexBoundsBuilderTest, TypeArrayWithAdditionalTypesHasOpenBounds) { ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, TypeStringOrNumberHasCorrectBounds) { +TEST_F(IndexBoundsBuilderTest, TypeStringOrNumberHasCorrectBounds) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$type: ['string', 'number']}}"); auto expr = parseMatchExpression(obj); @@ -3029,7 +1273,7 @@ TEST(IndexBoundsBuilderTest, TypeStringOrNumberHasCorrectBounds) { ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, RedundantTypeNumberHasCorrectBounds) { +TEST_F(IndexBoundsBuilderTest, RedundantTypeNumberHasCorrectBounds) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$type: ['number', 'int', 'long', 'double']}}"); auto expr = parseMatchExpression(obj); @@ -3047,49 +1291,49 @@ TEST(IndexBoundsBuilderTest, RedundantTypeNumberHasCorrectBounds) { ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); } -TEST(IndexBoundsBuilderTest, CanUseCoveredMatchingForEqualityPredicate) { +TEST_F(IndexBoundsBuilderTest, CanUseCoveredMatchingForEqualityPredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$eq: 3}}"); auto expr = parseMatchExpression(obj); ASSERT_TRUE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToArrayPredicate) { +TEST_F(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToArrayPredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$eq: [1, 2, 3]}}"); auto expr = parseMatchExpression(obj); ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToNullPredicate) { +TEST_F(IndexBoundsBuilderTest, CannotUseCoveredMatchingForEqualityToNullPredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: null}"); auto expr = parseMatchExpression(obj); ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForTypeArrayPredicate) { +TEST_F(IndexBoundsBuilderTest, CannotUseCoveredMatchingForTypeArrayPredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$type: 'array'}}"); auto expr = parseMatchExpression(obj); ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsTruePredicate) { +TEST_F(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsTruePredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$exists: true}}"); auto expr = parseMatchExpression(obj); ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsFalsePredicate) { +TEST_F(IndexBoundsBuilderTest, CannotUseCoveredMatchingForExistsFalsePredicate) { auto testIndex = buildSimpleIndexEntry(); BSONObj obj = fromjson("{a: {$exists: false}}"); auto expr = parseMatchExpression(obj); ASSERT_FALSE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, CanUseCoveredMatchingForExistsTrueWithSparseIndex) { +TEST_F(IndexBoundsBuilderTest, CanUseCoveredMatchingForExistsTrueWithSparseIndex) { auto testIndex = buildSimpleIndexEntry(); testIndex.sparse = true; BSONObj obj = fromjson("{a: {$exists: true}}"); @@ -3097,7 +1341,7 @@ TEST(IndexBoundsBuilderTest, CanUseCoveredMatchingForExistsTrueWithSparseIndex) ASSERT_TRUE(IndexBoundsBuilder::canUseCoveredMatching(expr.get(), testIndex)); } -TEST(IndexBoundsBuilderTest, IntersectizeBasic) { +TEST_F(IndexBoundsBuilderTest, IntersectizeBasic) { OrderedIntervalList oil1("xyz"); oil1.intervals = {Interval(BSON("" << 0 << "" << 5), false, false)}; diff --git a/src/mongo/db/query/index_bounds_builder_test.h b/src/mongo/db/query/index_bounds_builder_test.h new file mode 100644 index 00000000000..1080abe7198 --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_test.h @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/matcher/expression_parser.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/query/index_bounds_builder.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +class IndexBoundsBuilderTest : public unittest::Test { +public: + /** + * Utility function to create MatchExpression + */ + std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + StatusWithMatchExpression status = MatchExpressionParser::parse(obj, std::move(expCtx)); + ASSERT_TRUE(status.isOK()); + return std::unique_ptr<MatchExpression>(status.getValue().release()); + } + + /** + * Make a minimal IndexEntry from just an optional key pattern. A dummy name will be added. An + * empty key pattern will be used if none is provided. + */ + IndexEntry buildSimpleIndexEntry(const BSONObj& kp = BSONObj()) { + return {kp, + IndexNames::nameToType(IndexNames::findPluginName(kp)), + false, + {}, + {}, + false, + false, + CoreIndexInfo::Identifier("test_foo"), + nullptr, + {}, + nullptr, + nullptr}; + } + + /** + * Given a list of queries in 'toUnion', translate into index bounds and return + * the union of these bounds in the out-parameter 'oilOut'. + */ + void testTranslateAndUnion(const std::vector<BSONObj>& toUnion, + OrderedIntervalList* oilOut, + IndexBoundsBuilder::BoundsTightness* tightnessOut) { + auto testIndex = buildSimpleIndexEntry(); + + for (auto it = toUnion.begin(); it != toUnion.end(); ++it) { + auto expr = parseMatchExpression(*it); + BSONElement elt = it->firstElement(); + if (toUnion.begin() == it) { + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); + } else { + IndexBoundsBuilder::translateAndUnion( + expr.get(), elt, testIndex, oilOut, tightnessOut); + } + } + } + + /** + * Given a list of queries in 'toUnion', translate into index bounds and return + * the intersection of these bounds in the out-parameter 'oilOut'. + */ + void testTranslateAndIntersect(const std::vector<BSONObj>& toIntersect, + OrderedIntervalList* oilOut, + IndexBoundsBuilder::BoundsTightness* tightnessOut) { + auto testIndex = buildSimpleIndexEntry(); + + for (auto it = toIntersect.begin(); it != toIntersect.end(); ++it) { + auto expr = parseMatchExpression(*it); + BSONElement elt = it->firstElement(); + if (toIntersect.begin() == it) { + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); + } else { + IndexBoundsBuilder::translateAndIntersect( + expr.get(), elt, testIndex, oilOut, tightnessOut); + } + } + } + + /** + * 'constraints' is a vector of BSONObj's representing match expressions, where + * each filter is paired with a boolean. If the boolean is true, then the filter's + * index bounds should be intersected with the other constraints; if false, then + * they should be unioned. The resulting bounds are returned in the + * out-parameter 'oilOut'. + */ + void testTranslate(const std::vector<std::pair<BSONObj, bool>>& constraints, + OrderedIntervalList* oilOut, + IndexBoundsBuilder::BoundsTightness* tightnessOut) { + auto testIndex = buildSimpleIndexEntry(); + + for (auto it = constraints.begin(); it != constraints.end(); ++it) { + BSONObj obj = it->first; + bool isIntersect = it->second; + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + if (constraints.begin() == it) { + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, oilOut, tightnessOut); + } else if (isIntersect) { + IndexBoundsBuilder::translateAndIntersect( + expr.get(), elt, testIndex, oilOut, tightnessOut); + } else { + IndexBoundsBuilder::translateAndUnion( + expr.get(), elt, testIndex, oilOut, tightnessOut); + } + } + } + + /** + * Get a BSONObj which represents the interval from MinKey to 'end'. + */ + BSONObj minKeyIntObj(int end) { + BSONObjBuilder bob; + bob.appendMinKey(""); + bob.appendNumber("", end); + return bob.obj(); + } + + /** + * Get a BSONObj which represents the interval from 'start' to MaxKey. + */ + BSONObj maxKeyIntObj(int start) { + BSONObjBuilder bob; + bob.appendNumber("", start); + bob.appendMaxKey(""); + return bob.obj(); + } +}; + +} // namespace mongo diff --git a/src/mongo/db/query/index_bounds_builder_type_test.cpp b/src/mongo/db/query/index_bounds_builder_type_test.cpp new file mode 100644 index 00000000000..b943a9ce43b --- /dev/null +++ b/src/mongo/db/query/index_bounds_builder_type_test.cpp @@ -0,0 +1,158 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/query/index_bounds_builder_test.h" + +#include <limits> + +namespace mongo { +namespace { + +using DoubleLimits = std::numeric_limits<double>; + +TEST_F(IndexBoundsBuilderTest, TypeNumber) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: {$type: 'number'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + + // Build the expected interval. + BSONObjBuilder bob; + BSONType type = BSONType::NumberInt; + bob.appendMinForType("", type); + bob.appendMaxForType("", type); + BSONObj expectedInterval = bob.obj(); + + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(expectedInterval, true, true))); + ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT); +} + +// Test $type bounds for Code BSON type. +TEST_F(IndexBoundsBuilderTest, CodeTypeBounds) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: {$type: 13}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + // Build the expected interval. + BSONObjBuilder bob; + bob.appendCode("", ""); + bob.appendCodeWScope("", "", BSONObj()); + BSONObj expectedInterval = bob.obj(); + + // Check the output of translate(). + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(expectedInterval, true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +// Test $type bounds for Code With Scoped BSON type. +TEST_F(IndexBoundsBuilderTest, CodeWithScopeTypeBounds) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: {$type: 15}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + // Build the expected interval. + BSONObjBuilder bob; + bob.appendCodeWScope("", "", BSONObj()); + bob.appendMaxKey(""); + BSONObj expectedInterval = bob.obj(); + + // Check the output of translate(). + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(expectedInterval, true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +// Test $type bounds for double BSON type. +TEST_F(IndexBoundsBuilderTest, DoubleTypeBounds) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: {$type: 1}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + // Build the expected interval. + BSONObjBuilder bob; + bob.appendNumber("", DoubleLimits::quiet_NaN()); + bob.appendNumber("", DoubleLimits::infinity()); + BSONObj expectedInterval = bob.obj(); + + // Check the output of translate(). + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(Interval(expectedInterval, true, true))); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +TEST_F(IndexBoundsBuilderTest, TypeArrayBounds) { + auto testIndex = buildSimpleIndexEntry(); + BSONObj obj = fromjson("{a: {$type: 'array'}}"); + auto expr = parseMatchExpression(obj); + BSONElement elt = obj.firstElement(); + + OrderedIntervalList oil; + IndexBoundsBuilder::BoundsTightness tightness; + IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness); + + // Check the output of translate(). + ASSERT_EQUALS(oil.name, "a"); + ASSERT_EQUALS(oil.intervals.size(), 1U); + ASSERT_EQUALS(Interval::INTERVAL_EQUALS, + oil.intervals[0].compare(IndexBoundsBuilder::allValues())); + ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH); +} + +} // namespace +} // namespace mongo |