diff options
-rw-r--r-- | src/mongo/db/pipeline/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp | 78 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_internal_unpack_bucket.h | 18 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp (renamed from src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/map_predicates_on_control_field_test.cpp) | 237 |
4 files changed, 291 insertions, 44 deletions
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index aaed2a2b3b6..4914a74c8aa 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -392,8 +392,8 @@ env.CppUnitTest( 'document_source_sort_test.cpp', 'document_source_union_with_test.cpp', 'document_source_internal_unpack_bucket_test/extract_or_build_project_to_internalize_test.cpp', + 'document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp', 'document_source_internal_unpack_bucket_test/internalize_project_test.cpp', - 'document_source_internal_unpack_bucket_test/map_predicates_on_control_field_test.cpp', 'document_source_internal_unpack_bucket_test/unpack_bucket_exec_test.cpp', 'document_source_unwind_test.cpp', 'expression_and_test.cpp', diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp index dfb3e67185a..beba31ecc55 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.cpp @@ -108,6 +108,39 @@ void optimizeEndOfPipeline(Pipeline::SourceContainer::iterator itr, container->erase(std::next(itr), container->end()); container->splice(std::next(itr), endOfPipeline); } + +/** + * Creates an ObjectId initialized with an appropriate timestamp corresponding to 'matchExpr' and + * returns it as a Value. + */ +Value constructObjectIdValue(const ComparisonMatchExpression* matchExpr) { + // An ObjectId consists of a 4-byte timestamp, as well as a unique value and a counter, thus + // two ObjectIds initialized with the same date will have different values. To ensure that we + // do not incorrectly include or exclude any buckets, depending on the operator we will + // construct either the largest or the smallest ObjectId possible with the corresponding date. + OID oid; + if (matchExpr->getData().type() == BSONType::Date) { + switch (matchExpr->matchType()) { + case MatchExpression::LT: { + oid.init(matchExpr->getData().date(), false /* min */); + break; + } + case MatchExpression::LTE: + case MatchExpression::EQ: { + oid.init(matchExpr->getData().date(), true /* max */); + break; + } + default: + // We will only perform this optimization with query operators $lt, $lte and $eq. + MONGO_UNREACHABLE_TASSERT(5375801); + } + } + // If the query operand is not of type Date, the original query will not match on any documents + // because documents in a time-series collection must have a timeField of type Date. We will + // make this case faster by keeping the ObjectId as the lowest possible value so as to + // eliminate all buckets. + return Value(oid); +} } // namespace void BucketUnpacker::reset(BSONObj&& bucket) { @@ -365,7 +398,7 @@ std::pair<BSONObj, bool> DocumentSourceInternalUnpackBucket::extractOrBuildProje } std::unique_ptr<MatchExpression> createComparisonPredicate( - const ComparisonMatchExpression* matchExpr, const boost::optional<std::string>& metaField) { + const ComparisonMatchExpression* matchExpr, const BucketSpec& bucketSpec) { auto path = matchExpr->path(); auto rhs = matchExpr->getData(); @@ -385,8 +418,9 @@ std::unique_ptr<MatchExpression> createComparisonPredicate( } // We must avoid mapping predicates on the meta field onto the control field. - if (metaField && - (path == metaField.get() || expression::isPathPrefixOf(metaField.get(), path))) { + if (bucketSpec.metaField && + (path == bucketSpec.metaField.get() || + expression::isPathPrefixOf(bucketSpec.metaField.get(), path))) { return nullptr; } @@ -400,6 +434,11 @@ std::unique_ptr<MatchExpression> createComparisonPredicate( andMatchExpr->add(std::make_unique<InternalExprGTEMatchExpression>( str::stream() << DocumentSourceInternalUnpackBucket::kControlMaxFieldName << path, rhs)); + + if (path == bucketSpec.timeField) { + andMatchExpr->add(std::make_unique<LTEMatchExpression>( + BucketUnpacker::kBucketIdFieldName, constructObjectIdValue(matchExpr))); + } return andMatchExpr; } case MatchExpression::GT: { @@ -413,14 +452,34 @@ std::unique_ptr<MatchExpression> createComparisonPredicate( rhs); } case MatchExpression::LT: { - return std::make_unique<InternalExprLTMatchExpression>( + auto controlPred = std::make_unique<InternalExprLTMatchExpression>( str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldName << path, rhs); + if (path == bucketSpec.timeField) { + auto andMatchExpr = std::make_unique<AndMatchExpression>(); + + andMatchExpr->add(std::make_unique<LTMatchExpression>( + BucketUnpacker::kBucketIdFieldName, constructObjectIdValue(matchExpr))); + andMatchExpr->add(controlPred.release()); + + return andMatchExpr; + } + return controlPred; } case MatchExpression::LTE: { - return std::make_unique<InternalExprLTEMatchExpression>( + auto controlPred = std::make_unique<InternalExprLTEMatchExpression>( str::stream() << DocumentSourceInternalUnpackBucket::kControlMinFieldName << path, rhs); + if (path == bucketSpec.timeField) { + auto andMatchExpr = std::make_unique<AndMatchExpression>(); + + andMatchExpr->add(std::make_unique<LTEMatchExpression>( + BucketUnpacker::kBucketIdFieldName, constructObjectIdValue(matchExpr))); + andMatchExpr->add(controlPred.release()); + + return andMatchExpr; + } + return controlPred; } default: MONGO_UNREACHABLE_TASSERT(5348302); @@ -429,14 +488,15 @@ std::unique_ptr<MatchExpression> createComparisonPredicate( MONGO_UNREACHABLE_TASSERT(5348303); } -std::unique_ptr<MatchExpression> DocumentSourceInternalUnpackBucket::createPredicatesOnControlField( +std::unique_ptr<MatchExpression> +DocumentSourceInternalUnpackBucket::createPredicatesOnBucketLevelField( const MatchExpression* matchExpr) const { if (matchExpr->matchType() == MatchExpression::AND) { auto nextAnd = static_cast<const AndMatchExpression*>(matchExpr); auto andMatchExpr = std::make_unique<AndMatchExpression>(); for (size_t i = 0; i < nextAnd->numChildren(); i++) { - if (auto child = createPredicatesOnControlField(nextAnd->getChild(i))) { + if (auto child = createPredicatesOnBucketLevelField(nextAnd->getChild(i))) { andMatchExpr->add(std::move(child)); } } @@ -445,7 +505,7 @@ std::unique_ptr<MatchExpression> DocumentSourceInternalUnpackBucket::createPredi } } else if (ComparisonMatchExpression::isComparisonMatchExpression(matchExpr)) { return createComparisonPredicate(static_cast<const ComparisonMatchExpression*>(matchExpr), - _bucketUnpacker.bucketSpec().metaField); + _bucketUnpacker.bucketSpec()); } return nullptr; @@ -464,7 +524,7 @@ Pipeline::SourceContainer::iterator DocumentSourceInternalUnpackBucket::doOptimi // Attempt to map predicates on bucketed fields to predicates on the control field. if (auto nextMatch = dynamic_cast<DocumentSourceMatch*>((*std::next(itr)).get())) { - if (auto match = createPredicatesOnControlField(nextMatch->getMatchExpression())) { + if (auto match = createPredicatesOnBucketLevelField(nextMatch->getMatchExpression())) { // Optimize the newly created MatchExpression. auto optimized = MatchExpression::optimize(std::move(match)); BSONObjBuilder bob; diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h index 763771d9f2f..69988aa5549 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket.h @@ -206,14 +206,22 @@ public: Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) const; /** - * Takes a predicate after $_internalUnpackBucket on a bucketed field as an argument, and - * attempts to map it to a new predicate on the control field. For example, the predicate {a: - * {$gt: 5}} will generate the predicate {control.max.a: {$_internalExprGt: 5}}, which will be - * added before the $_internalUnpackBucket stage. + * Takes a predicate after $_internalUnpackBucket on a bucketed field as an argument and + * attempts to map it to a new predicate on the 'control' field. For example, the predicate + * {a: {$gt: 5}} will generate the predicate {control.max.a: {$_internalExprGt: 5}}, which will + * be added before the $_internalUnpackBucket stage. + * + * If the original predicate is on the bucket's timeField we may also create a new predicate + * on the '_id' field to assist in index utilization. For example, the predicate + * {time: {$lt: new Date(...)}} will generate the following predicate: + * {$and: [ + * {_id: {$lt: ObjectId(...)}}, + * {control.min.time: {$_internalExprLt: new Date(...)}} + * ]} * * If the provided predicate is ineligible for this mapping, the function will return a nullptr. */ - std::unique_ptr<MatchExpression> createPredicatesOnControlField( + std::unique_ptr<MatchExpression> createPredicatesOnBucketLevelField( const MatchExpression* matchExpr) const; private: diff --git a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/map_predicates_on_control_field_test.cpp b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp index bd4360ac2f0..88e5a2a2109 100644 --- a/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/map_predicates_on_control_field_test.cpp +++ b/src/mongo/db/pipeline/document_source_internal_unpack_bucket_test/create_predicates_on_bucket_level_field_test.cpp @@ -52,7 +52,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{'control.max.a': {$_internalExprGt: 1}}")); @@ -70,7 +70,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{'control.max.a': {$_internalExprGte: 1}}")); @@ -88,7 +88,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{'control.min.a': {$_internalExprLt: 1}}")); @@ -106,7 +106,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{'control.min.a': {$_internalExprLte: 1}}")); @@ -124,7 +124,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{$and: [{'control.min.a': {$_internalExprLte: 1}}, " @@ -135,7 +135,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsAndWithPushableChildrenOnControlField) { auto pipeline = Pipeline::parse( makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), - fromjson("{$match: {$and: [{time: {$gt: 1}}, {a: {$lt: 5}}]}}")), + fromjson("{$match: {$and: [{b: {$gt: 1}}, {a: {$lt: 5}}]}}")), getExpCtx()); auto& container = pipeline->getSources(); @@ -143,10 +143,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), - fromjson("{$and: [{'control.max.time': {$_internalExprGt: 1}}, " + fromjson("{$and: [{'control.max.b': {$_internalExprGt: 1}}, " "{'control.min.a': {$_internalExprLt: 5}}]}")); } @@ -154,7 +154,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeDoesNotMapAndWithUnpushableChildrenOnControlField) { auto pipeline = Pipeline::parse( makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), - fromjson("{$match: {$and: [{time: {$ne: 1}}, {a: {$ne: 5}}]}}")), + fromjson("{$match: {$and: [{b: {$ne: 1}}, {a: {$ne: 5}}]}}")), getExpCtx()); auto& container = pipeline->getSources(); @@ -162,7 +162,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -171,7 +171,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsAndWithPushableAndUnpushableChildrenOnControlField) { auto pipeline = Pipeline::parse( makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), - fromjson("{$match: {$and: [{time: {$gt: 1}}, {a: {$ne: 5}}]}}")), + fromjson("{$match: {$and: [{b: {$gt: 1}}, {a: {$ne: 5}}]}}")), getExpCtx()); auto& container = pipeline->getSources(); @@ -179,10 +179,10 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), - fromjson("{$and: [{'control.max.time': {$_internalExprGt: 1}}]}")); + fromjson("{$and: [{'control.max.b': {$_internalExprGt: 1}}]}")); } TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, @@ -190,8 +190,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto pipeline = Pipeline::parse( makeVector( fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), - fromjson( - "{$match: {$and: [{b: {$gte: 2}}, {$and: [{time: {$gt: 1}}, {a: {$lt: 5}}]}]}}")), + fromjson("{$match: {$and: [{b: {$gte: 2}}, {$and: [{b: {$gt: 1}}, {a: {$lt: 5}}]}]}}")), getExpCtx()); auto& container = pipeline->getSources(); @@ -199,18 +198,18 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), fromjson("{$and: [{'control.max.b': {$_internalExprGte: 2}}, {$and: " - "[{'control.max.time': {$_internalExprGt: 1}}, " + "[{'control.max.b': {$_internalExprGt: 1}}, " "{'control.min.a': {$_internalExprLt: 5}}]}]}")); } TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeFurtherOptimizesNewlyAddedMatchWithSingletonAndNode) { auto unpackBucketObj = fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"); - auto matchObj = fromjson("{$match: {$and: [{time: {$gt: 1}}, {a: {$ne: 5}}]}}"); + auto matchObj = fromjson("{$match: {$and: [{b: {$gt: 1}}, {a: {$ne: 5}}]}}"); auto pipeline = Pipeline::parse(makeVector(unpackBucketObj, matchObj), getExpCtx()); ASSERT_EQ(pipeline->getSources().size(), 2U); @@ -220,7 +219,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto stages = pipeline->serializeToBson(); ASSERT_EQ(stages.size(), 3U); - ASSERT_BSONOBJ_EQ(stages[0], fromjson("{$match: {'control.max.time': {$_internalExprGt: 1}}}")); + ASSERT_BSONOBJ_EQ(stages[0], fromjson("{$match: {'control.max.b': {$_internalExprGt: 1}}}")); ASSERT_BSONOBJ_EQ(stages[1], unpackBucketObj); ASSERT_BSONOBJ_EQ(stages[2], matchObj); } @@ -229,7 +228,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeFurtherOptimizesNewlyAddedMatchWithNestedAndNodes) { auto unpackBucketObj = fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"); auto matchObj = - fromjson("{$match: {$and: [{b: {$gte: 2}}, {$and: [{time: {$gt: 1}}, {a: {$lt: 5}}]}]}}"); + fromjson("{$match: {$and: [{b: {$gte: 2}}, {$and: [{c: {$gt: 1}}, {a: {$lt: 5}}]}]}}"); auto pipeline = Pipeline::parse(makeVector(unpackBucketObj, matchObj), getExpCtx()); ASSERT_EQ(pipeline->getSources().size(), 2U); @@ -241,7 +240,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, ASSERT_BSONOBJ_EQ( stages[0], - fromjson("{$match: {$and: [{'control.max.b': {$_internalExprGte: 2}}, {'control.max.time': " + fromjson("{$match: {$and: [{'control.max.b': {$_internalExprGte: 2}}, {'control.max.c': " "{$_internalExprGt: 1}}, {'control.min.a': {$_internalExprLt: 5}}]}}")); ASSERT_BSONOBJ_EQ(stages[1], unpackBucketObj); ASSERT_BSONOBJ_EQ(stages[2], matchObj); @@ -259,7 +258,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -276,7 +275,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -293,7 +292,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -312,7 +311,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -331,7 +330,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT(predicate == nullptr); } @@ -342,7 +341,7 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, makeVector( fromjson( "{$_internalUnpackBucket: {exclude: [], timeField: 'time', metaField: 'myMeta'}}"), - fromjson("{$match: {$and: [{time: {$gt: 1}}, {myMeta: {$eq: 5}}]}}")), + fromjson("{$match: {$and: [{a: {$gt: 1}}, {myMeta: {$eq: 5}}]}}")), getExpCtx()); auto& container = pipeline->getSources(); @@ -350,10 +349,190 @@ TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) - ->createPredicatesOnControlField(original->getMatchExpression()); + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); ASSERT_BSONOBJ_EQ(predicate->serialize(true), - fromjson("{$and: [{'control.max.time': {$_internalExprGt: 1}}]}")); + fromjson("{$and: [{'control.max.a': {$_internalExprGt: 1}}]}")); +} + +TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, OptimizeMapsTimePredicatesOnId) { + auto date = Date_t::now(); + { + auto timePred = BSON("$match" << BSON("time" << BSON("$lt" << date))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[0]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(date); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) == 0); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + BSON("control.min.time" << BSON("$_internalExprLt" << date))); + } + { + auto timePred = BSON("$match" << BSON("time" << BSON("$lte" << date))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[0]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(date); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) < 0); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + BSON("control.min.time" << BSON("$_internalExprLte" << date))); + } + { + auto timePred = BSON("$match" << BSON("time" << BSON("$eq" << date))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + + ASSERT_BSONOBJ_EQ(children.get()[0]->serialize(true), + BSON("control.min.time" << BSON("$_internalExprLte" << date))); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + BSON("control.max.time" << BSON("$_internalExprGte" << date))); + + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[2]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(date); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) < 0); + } +} + +TEST_F(InternalUnpackBucketPredicateMappingOptimizationTest, + OptimizeMapsTimePredicatesWithNonDateTypeOnId) { + { + auto timePred = BSON("$match" << BSON("time" << BSON("$lt" << 1))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[0]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(Date_t::min()); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) > 0); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + fromjson("{'control.min.time': {$_internalExprLt: 1}}")); + } + { + auto timePred = BSON("$match" << BSON("time" << BSON("$lte" << 1))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[0]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(Date_t::min()); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) > 0); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + fromjson("{'control.min.time': {$_internalExprLte: 1}}")); + } + { + auto timePred = BSON("$match" << BSON("time" << BSON("$eq" << 1))); + auto pipeline = Pipeline::parse( + makeVector(fromjson("{$_internalUnpackBucket: {exclude: [], timeField: 'time'}}"), + timePred), + getExpCtx()); + auto& container = pipeline->getSources(); + + ASSERT_EQ(pipeline->getSources().size(), 2U); + + auto original = dynamic_cast<DocumentSourceMatch*>(container.back().get()); + auto predicate = dynamic_cast<DocumentSourceInternalUnpackBucket*>(container.front().get()) + ->createPredicatesOnBucketLevelField(original->getMatchExpression()); + auto andExpr = dynamic_cast<AndMatchExpression*>(predicate.get()); + auto children = andExpr->getChildVector(); + + ASSERT_BSONOBJ_EQ(children.get()[0]->serialize(true), + fromjson("{'control.min.time': {$_internalExprLte: 1}}")); + + ASSERT_BSONOBJ_EQ(children.get()[1]->serialize(true), + fromjson("{'control.max.time': {$_internalExprGte: 1}}")); + + auto idPred = dynamic_cast<ComparisonMatchExpressionBase*>(children.get()[2]); + + ASSERT_EQ(idPred->path(), "_id"_sd); + ASSERT_EQ(idPred->getData().type(), BSONType::jstOID); + + OID oid; + oid.init(Date_t::min()); + ASSERT_TRUE(oid.compare(idPred->getData().OID()) > 0); + } } } // namespace |