summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Saskin <sophie.saskin@mongodb.com>2019-12-20 19:08:01 +0000
committerevergreen <evergreen@mongodb.com>2019-12-20 19:08:01 +0000
commit21abe36245600807b68198de636d368f96e669d7 (patch)
treec0894abe7ea44d2c3cbbb91f02ff342605265ba0
parent3fea6b339770dcdead06803b0c794553c25b94fb (diff)
downloadmongo-21abe36245600807b68198de636d368f96e669d7.tar.gz
SERVER-41700 query $type:xxx defaults to exact bounds or inexact covered bounds whenever possible
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--jstests/core/distinct_multikey_dotted_path.js9
-rw-r--r--jstests/core/index_large_and_small_dates.js30
-rw-r--r--src/mongo/bson/bsonobjbuilder.cpp7
-rw-r--r--src/mongo/bson/bsontypes.h34
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp58
-rw-r--r--src/mongo/db/query/index_bounds_builder_collator_test.cpp4
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp15
-rw-r--r--src/mongo/db/query/index_bounds_builder_type_test.cpp102
-rw-r--r--src/mongo/db/query/query_planner_collation_test.cpp2
19 files changed, 241 insertions, 30 deletions
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
index 1db676255a1..13bd9e220c1 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml
@@ -104,6 +104,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
index 4b6b3e90797..9ac98e83447 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml
@@ -103,6 +103,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
index 99d679ff1e2..3a6750cf447 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml
@@ -103,6 +103,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
index 3b429f26756..4f2c06f8c5b 100644
--- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml
@@ -103,6 +103,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
index ea539eeb1bc..638e6394fe9 100644
--- a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
+++ b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml
@@ -108,6 +108,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
index c606d4764d7..c0a905466c1 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
@@ -38,6 +38,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
index efc8d5e21c9..69571aa1262 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml
@@ -37,6 +37,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
index 6eea1c6f260..eb8027ddeb7 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml
@@ -37,6 +37,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
index 5f9ca4330ee..a2b66e8ea09 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml
@@ -37,6 +37,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
index 807e4c4785b..db03bdf71c0 100644
--- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml
@@ -73,6 +73,7 @@ selector:
- jstests/core/in.js
- jstests/core/index8.js # No explicit check for failed command.
- jstests/core/index_decimal.js
+ - jstests/core/index_large_and_small_dates.js
- jstests/core/index_multiple_compatibility.js
- jstests/core/index_partial_write_ops.js
- jstests/core/indexa.js # No explicit check for failed command.
diff --git a/jstests/core/distinct_multikey_dotted_path.js b/jstests/core/distinct_multikey_dotted_path.js
index b06ca2d95ea..13b0b8afa5c 100644
--- a/jstests/core/distinct_multikey_dotted_path.js
+++ b/jstests/core/distinct_multikey_dotted_path.js
@@ -182,14 +182,17 @@ assert.commandWorked(coll.insert({a: {b: {c: []}}}));
// only treat '0' as a field name (not array index).
})();
-// Creating an index on "a.b.0" and doing a distinct on it should use an IXSCAN, as "a.b" is
-// multikey. See explanation above about why a DISTINCT_SCAN cannot be used when the path
-// given is multikey.
+// Inserting an array on "a", creating an index on "a.b.0", and doing a distinct on it should use an
+// IXSCAN, as "a" is now multikey. See explanation above about why a DISTINCT_SCAN cannot be used
+// when the path given is multikey.
(function testDistinctWithPredOnNumericMultikeyPathWithIndex() {
const pred = {"a.b.0": {$type: "object"}};
const res = coll.distinct("a.b.0", pred);
assert.sameMembers(res, [{c: 4}]);
+ // Make "a" multikey in order to ensure that a DISTINCT_SCAN plan on "a.b.0" is not legal.
+ assert.commandWorked(coll.insert({a: [1, 2, 3]}));
+
const expl = coll.explain().distinct("a.b.0", pred);
assert.eq(false, planHasStage(db, expl.queryPlanner.winningPlan, "DISTINCT_SCAN"), expl);
assert.eq(true, planHasStage(db, expl.queryPlanner.winningPlan, "IXSCAN"), expl);
diff --git a/jstests/core/index_large_and_small_dates.js b/jstests/core/index_large_and_small_dates.js
new file mode 100644
index 00000000000..759f5b762a2
--- /dev/null
+++ b/jstests/core/index_large_and_small_dates.js
@@ -0,0 +1,30 @@
+(function() {
+"use strict";
+const coll = db.index_dates;
+coll.drop();
+
+// Min value for JS Date().
+const d1 = new Date(-8640000000000000);
+assert.commandWorked(coll.insert({_id: 1, d: d1}));
+// Max value for JS Date().
+const d2 = new Date(8640000000000000);
+assert.commandWorked(coll.insert({_id: 2, d: d2}));
+
+assert.commandWorked(coll.insert({_id: 3, d: 100}));
+
+function test() {
+ const list = coll.find({d: {$type: "date"}}).sort({_id: 1}).toArray();
+ assert.eq(2, list.length);
+ assert.eq(list[0], {_id: 1, d: d1});
+ assert.eq(list[1], {_id: 2, d: d2});
+}
+
+test();
+// Testing index version 1.
+assert.commandWorked(coll.createIndex({d: 1}, {v: 1}));
+test();
+assert.commandWorked(coll.dropIndex({d: 1}));
+// Testing index version 2.
+assert.commandWorked(coll.createIndex({d: 1}));
+test();
+})(); \ No newline at end of file
diff --git a/src/mongo/bson/bsonobjbuilder.cpp b/src/mongo/bson/bsonobjbuilder.cpp
index 369f307beb4..13133acf5c8 100644
--- a/src/mongo/bson/bsonobjbuilder.cpp
+++ b/src/mongo/bson/bsonobjbuilder.cpp
@@ -54,9 +54,7 @@ BSONObjBuilder& BSONObjBuilder::appendMinForType(StringData fieldName, int t) {
append(fieldName, "");
return *this;
case Date:
- // min varies with V0 and V1 indexes, so we go one type lower.
- appendBool(fieldName, true);
- // appendDate( fieldName , numeric_limits<long long>::min() );
+ appendDate(fieldName, Date_t::min());
return *this;
case bsonTimestamp:
appendTimestamp(fieldName, 0);
@@ -125,8 +123,7 @@ BSONObjBuilder& BSONObjBuilder::appendMaxForType(StringData fieldName, int t) {
appendMinForType(fieldName, Object);
return *this;
case Date:
- appendDate(fieldName,
- Date_t::fromMillisSinceEpoch(std::numeric_limits<long long>::max()));
+ appendDate(fieldName, Date_t::max());
return *this;
case bsonTimestamp:
append(fieldName, Timestamp::max());
diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h
index 43cf66369dd..68867eacf88 100644
--- a/src/mongo/bson/bsontypes.h
+++ b/src/mongo/bson/bsontypes.h
@@ -152,6 +152,40 @@ inline bool isNumericBSONType(BSONType type) {
}
}
+/**
+ * Given a type, returns whether or not that type has a variable width.
+ **/
+inline bool isVariableWidthType(BSONType type) {
+ switch (type) {
+ case Array:
+ case BinData:
+ case Code:
+ case CodeWScope:
+ case DBRef:
+ case Object:
+ case RegEx:
+ case String:
+ case Symbol:
+ return true;
+ case Bool:
+ case bsonTimestamp:
+ case Date:
+ case EOO:
+ case jstNULL:
+ case jstOID:
+ case MaxKey:
+ case MinKey:
+ case NumberDecimal:
+ case NumberDouble:
+ case NumberInt:
+ case NumberLong:
+ case Undefined:
+ return false;
+ default:
+ MONGO_UNREACHABLE;
+ }
+}
+
/* subtypes of BinData.
bdtCustom and above are ones that the JS compiler understands, but are
opaque to the database.
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index 90983655d7e..04cf9f79b05 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -35,6 +35,7 @@
#include <limits>
#include "mongo/base/string_data.h"
+#include "mongo/bson/bsontypes.h"
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/s2.h"
#include "mongo/db/index/expression_params.h"
@@ -379,6 +380,47 @@ void IndexBoundsBuilder::translate(const MatchExpression* expr,
}
}
+namespace {
+IndexBoundsBuilder::BoundsTightness computeTightnessForTypeSet(const MatcherTypeSet& typeSet,
+ const IndexEntry& index) {
+ // The Array case will not be handled because a typeSet with Array should not reach this
+ // function
+ invariant(!typeSet.hasType(BSONType::Array));
+
+ // The String and Object types with collation require an inexact fetch.
+ if (index.collator != nullptr &&
+ (typeSet.hasType(BSONType::String) || typeSet.hasType(BSONType::Object))) {
+ return IndexBoundsBuilder::INEXACT_FETCH;
+ }
+
+ // Null and Undefined Types always require an inexact fetch.
+ if (typeSet.hasType(BSONType::jstNULL) || typeSet.hasType(BSONType::Undefined)) {
+ return IndexBoundsBuilder::INEXACT_FETCH;
+ }
+
+ const auto numberTypesIncluded = static_cast<int>(typeSet.hasType(BSONType::NumberInt)) +
+ static_cast<int>(typeSet.hasType(BSONType::NumberLong)) +
+ static_cast<int>(typeSet.hasType(BSONType::NumberDecimal)) +
+ static_cast<int>(typeSet.hasType(BSONType::NumberDouble));
+
+ // Checks that either all the number types are present or "number" is present in the type set.
+ const bool hasAllNumbers = (numberTypesIncluded == 4) || typeSet.allNumbers;
+ const bool hasAnyNumbers = numberTypesIncluded > 0;
+
+ if (hasAnyNumbers && !hasAllNumbers) {
+ return IndexBoundsBuilder::INEXACT_COVERED;
+ }
+
+ // This check is effectively typeSet.hasType(BSONType::String) XOR
+ // typeSet.hasType(BSONType::Symbol).
+ if ((typeSet.hasType(BSONType::String) != typeSet.hasType(BSONType::Symbol))) {
+ return IndexBoundsBuilder::INEXACT_COVERED;
+ }
+
+ return IndexBoundsBuilder::EXACT;
+}
+} // namespace
+
void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
const BSONElement& elt,
const IndexEntry& index,
@@ -702,15 +744,17 @@ void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
BSONObjBuilder bob;
bob.appendMinForType("", type);
bob.appendMaxForType("", type);
- oilOut->intervals.push_back(
- makeRangeInterval(bob.obj(), BoundInclusion::kIncludeBothStartAndEndKeys));
+
+ // Types with variable width use the smallest value of the next type as their upper
+ // bound, so the upper bound needs to be excluded.
+ auto boundInclusionRule = BoundInclusion::kIncludeBothStartAndEndKeys;
+ if (isVariableWidthType(type)) {
+ boundInclusionRule = BoundInclusion::kIncludeStartKeyOnly;
+ }
+ oilOut->intervals.push_back(makeRangeInterval(bob.obj(), boundInclusionRule));
}
- // If we're only matching the "number" type, then the bounds are exact. Otherwise, the
- // bounds may be inexact.
- *tightnessOut = (tme->typeSet().isSingleType() && tme->typeSet().allNumbers)
- ? IndexBoundsBuilder::EXACT
- : IndexBoundsBuilder::INEXACT_FETCH;
+ *tightnessOut = computeTightnessForTypeSet(tme->typeSet(), index);
// Sort the intervals, and merge redundant ones.
unionize(oilOut);
diff --git a/src/mongo/db/query/index_bounds_builder_collator_test.cpp b/src/mongo/db/query/index_bounds_builder_collator_test.cpp
index 9c697337740..952b2c73ac2 100644
--- a/src/mongo/db/query/index_bounds_builder_collator_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_collator_test.cpp
@@ -406,7 +406,7 @@ TEST_F(IndexBoundsBuilderTest, ExistsFalseWithMockCollatorIsInexactFetch) {
ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
}
-TEST_F(IndexBoundsBuilderTest, TypeStringIsInexactFetch) {
+TEST_F(IndexBoundsBuilderTest, TypeStringWithCollatorIsInexactFetch) {
CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString);
auto testIndex = buildSimpleIndexEntry();
testIndex.collator = &collator;
@@ -422,7 +422,7 @@ TEST_F(IndexBoundsBuilderTest, TypeStringIsInexactFetch) {
ASSERT_EQUALS(oil.name, "a");
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
- oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, true)));
+ oil.intervals[0].compare(Interval(fromjson("{'': '', '': {}}"), true, false)));
ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
}
diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp
index 077ccd7c386..2d3ca2d0ac5 100644
--- a/src/mongo/db/query/index_bounds_builder_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_test.cpp
@@ -254,7 +254,8 @@ TEST_F(IndexBoundsBuilderTest, TranslateLtNegativeInfinity) {
TEST_F(IndexBoundsBuilderTest, TranslateLtDate) {
auto testIndex = buildSimpleIndexEntry();
- BSONObj obj = BSON("a" << LT << Date_t::fromMillisSinceEpoch(5000));
+ const auto date = Date_t::fromMillisSinceEpoch(5000);
+ BSONObj obj = BSON("a" << LT << date);
auto expr = parseMatchExpression(obj);
BSONElement elt = obj.firstElement();
OrderedIntervalList oil;
@@ -262,9 +263,9 @@ TEST_F(IndexBoundsBuilderTest, TranslateLtDate) {
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, '': new Date(5000)}"), false, false)));
+ ASSERT_EQUALS(
+ Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(BSON("" << Date_t::min() << "" << date), true, false)));
ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
}
@@ -1269,8 +1270,8 @@ TEST_F(IndexBoundsBuilderTest, TypeStringOrNumberHasCorrectBounds) {
Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': NaN, '': Infinity}"), true, true)));
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
- oil.intervals[1].compare(Interval(fromjson("{'': '', '': {}}"), true, true)));
- ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
+ oil.intervals[1].compare(Interval(fromjson("{'': '', '': {}}"), true, false)));
+ ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED);
}
TEST_F(IndexBoundsBuilderTest, RedundantTypeNumberHasCorrectBounds) {
@@ -1288,7 +1289,7 @@ TEST_F(IndexBoundsBuilderTest, RedundantTypeNumberHasCorrectBounds) {
ASSERT_EQUALS(
Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(fromjson("{'': NaN, '': Infinity}"), true, true)));
- ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT(tightness == IndexBoundsBuilder::EXACT);
}
TEST_F(IndexBoundsBuilderTest, CanUseCoveredMatchingForEqualityPredicate) {
diff --git a/src/mongo/db/query/index_bounds_builder_type_test.cpp b/src/mongo/db/query/index_bounds_builder_type_test.cpp
index b943a9ce43b..be1750725c0 100644
--- a/src/mongo/db/query/index_bounds_builder_type_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_type_test.cpp
@@ -82,8 +82,8 @@ TEST_F(IndexBoundsBuilderTest, CodeTypeBounds) {
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);
+ oil.intervals[0].compare(Interval(expectedInterval, true, false)));
+ ASSERT(tightness == IndexBoundsBuilder::EXACT);
}
// Test $type bounds for Code With Scoped BSON type.
@@ -107,8 +107,8 @@ TEST_F(IndexBoundsBuilderTest, CodeWithScopeTypeBounds) {
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);
+ oil.intervals[0].compare(Interval(expectedInterval, true, false)));
+ ASSERT(tightness == IndexBoundsBuilder::EXACT);
}
// Test $type bounds for double BSON type.
@@ -133,7 +133,7 @@ TEST_F(IndexBoundsBuilderTest, DoubleTypeBounds) {
ASSERT_EQUALS(oil.intervals.size(), 1U);
ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
oil.intervals[0].compare(Interval(expectedInterval, true, true)));
- ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
+ ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED);
}
TEST_F(IndexBoundsBuilderTest, TypeArrayBounds) {
@@ -154,5 +154,97 @@ TEST_F(IndexBoundsBuilderTest, TypeArrayBounds) {
ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
}
+TEST_F(IndexBoundsBuilderTest, TypeSymbolBounds) {
+ auto testIndex = buildSimpleIndexEntry();
+ BSONObj obj = fromjson("{a: {$type: 'symbol'}}");
+ 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(Interval(fromjson("{'': '', '': {}}"), true, false)));
+ ASSERT(tightness == IndexBoundsBuilder::INEXACT_COVERED);
+}
+
+TEST_F(IndexBoundsBuilderTest, TypeStringWithoutCollatorBounds) {
+ auto testIndex = buildSimpleIndexEntry();
+
+ 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, false)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_COVERED);
+}
+
+TEST_F(IndexBoundsBuilderTest, TypeSymbolAndStringBounds) {
+ auto testIndex = buildSimpleIndexEntry();
+ BSONObj obj = fromjson("{a: {$type: ['string', 'symbol']}}");
+ 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(Interval(fromjson("{'': '', '': {}}"), true, false)));
+ ASSERT(tightness == IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TypeNullBounds) {
+ auto testIndex = buildSimpleIndexEntry();
+ BSONObj obj = fromjson("{a: {$type: 'null'}}");
+ 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(Interval(fromjson("{'': null, '': null}"), true, true)));
+ ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
+}
+
+TEST_F(IndexBoundsBuilderTest, TypeUndefinedBounds) {
+ auto testIndex = buildSimpleIndexEntry();
+ BSONObj obj = fromjson("{a: {$type: 'undefined'}}");
+ 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(Interval(fromjson("{'': undefined, '': undefined}"), true, true)));
+ ASSERT(tightness == IndexBoundsBuilder::INEXACT_FETCH);
+}
+
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/query/query_planner_collation_test.cpp b/src/mongo/db/query/query_planner_collation_test.cpp
index 806e0c0f54d..8cdbc8a18b1 100644
--- a/src/mongo/db/query/query_planner_collation_test.cpp
+++ b/src/mongo/db/query/query_planner_collation_test.cpp
@@ -171,7 +171,7 @@ TEST_F(QueryPlannerTest, TypeStringCannotBeCoveredWithCollator) {
assertSolutionExists(
"{proj: {spec: {_id: 0, a: 1}, node: {fetch: {filter: {a:{$type:'string'}}, collation: "
"{locale: 'reverse'}, node: {ixscan: {pattern: {a: 1}, filter: null, "
- "bounds: {a: [['',{},true,true]]}}}}}}}");
+ "bounds: {a: [['',{},true,false]]}}}}}}}");
}
TEST_F(QueryPlannerTest, NotWithStringBoundsCannotBeCoveredWithCollator) {