From 21abe36245600807b68198de636d368f96e669d7 Mon Sep 17 00:00:00 2001 From: Sophie Saskin Date: Fri, 20 Dec 2019 19:08:01 +0000 Subject: SERVER-41700 query $type:xxx defaults to exact bounds or inexact covered bounds whenever possible --- ...ead_write_multi_stmt_txn_jscore_passthrough.yml | 1 + ...lti_shard_multi_stmt_txn_jscore_passthrough.yml | 1 + ...ti_stmt_txn_kill_primary_jscore_passthrough.yml | 1 + ...tmt_txn_stepdown_primary_jscore_passthrough.yml | 1 + ..._stmt_txn_jscore_passthrough_with_migration.yml | 1 + ...lica_sets_multi_stmt_txn_jscore_passthrough.yml | 1 + ...ti_stmt_txn_kill_primary_jscore_passthrough.yml | 1 + ..._multi_stmt_txn_stepdown_jscore_passthrough.yml | 1 + ...mt_txn_terminate_primary_jscore_passthrough.yml | 1 + .../sharded_multi_stmt_txn_jscore_passthrough.yml | 1 + jstests/core/distinct_multikey_dotted_path.js | 9 +- jstests/core/index_large_and_small_dates.js | 30 ++++++ src/mongo/bson/bsonobjbuilder.cpp | 7 +- src/mongo/bson/bsontypes.h | 34 +++++++ src/mongo/db/query/index_bounds_builder.cpp | 58 ++++++++++-- .../query/index_bounds_builder_collator_test.cpp | 4 +- src/mongo/db/query/index_bounds_builder_test.cpp | 15 +-- .../db/query/index_bounds_builder_type_test.cpp | 102 ++++++++++++++++++++- .../db/query/query_planner_collation_test.cpp | 2 +- 19 files changed, 241 insertions(+), 30 deletions(-) create mode 100644 jstests/core/index_large_and_small_dates.js 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::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::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 #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(typeSet.hasType(BSONType::NumberInt)) + + static_cast(typeSet.hasType(BSONType::NumberLong)) + + static_cast(typeSet.hasType(BSONType::NumberDecimal)) + + static_cast(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) { -- cgit v1.2.1