diff options
-rw-r--r-- | jstests/core/timeseries/timeseries_predicates.js | 204 | ||||
-rw-r--r-- | src/mongo/db/exec/bucket_unpacker.cpp | 19 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_algo.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_algo.h | 7 |
4 files changed, 133 insertions, 98 deletions
diff --git a/jstests/core/timeseries/timeseries_predicates.js b/jstests/core/timeseries/timeseries_predicates.js index 5499c060eea..d43f20a49e7 100644 --- a/jstests/core/timeseries/timeseries_predicates.js +++ b/jstests/core/timeseries/timeseries_predicates.js @@ -19,7 +19,7 @@ const tsColl = db.timeseries_predicates_timeseries; coll.drop(); tsColl.drop(); assert.commandWorked( - db.createCollection(tsColl.getName(), {timeseries: {timeField: 'time', metaField: 'meta'}})); + db.createCollection(tsColl.getName(), {timeseries: {timeField: 'time', metaField: 'mt'}})); const bucketsColl = db.getCollection('system.buckets.' + tsColl.getName()); // Test that 'predicate' behaves correctly on the example documents, @@ -88,18 +88,18 @@ checkAllBucketings({x: {$exists: true}}, [ // Test $or... { - // ... on metric + meta. + // ... on metric + mt. checkAllBucketings({ $or: [ {x: {$lt: 0}}, - {'meta.y': {$gt: 0}}, + {'mt.y': {$gt: 0}}, ] }, [ - {x: +1, meta: {y: -1}}, - {x: +1, meta: {y: +1}}, - {x: -1, meta: {y: -1}}, - {x: -1, meta: {y: +1}}, + {x: +1, mt: {y: -1}}, + {x: +1, mt: {y: +1}}, + {x: -1, mt: {y: -1}}, + {x: -1, mt: {y: +1}}, ]); // ... when one argument can't be pushed down. @@ -133,18 +133,18 @@ checkAllBucketings({x: {$exists: true}}, [ // Test $and... { - // ... on metric + meta. + // ... on metric + mt. checkAllBucketings({ $and: [ {x: {$lt: 0}}, - {'meta.y': {$gt: 0}}, + {'mt.y': {$gt: 0}}, ] }, [ - {x: +1, meta: {y: -1}}, - {x: +1, meta: {y: +1}}, - {x: -1, meta: {y: -1}}, - {x: -1, meta: {y: +1}}, + {x: +1, mt: {y: -1}}, + {x: +1, mt: {y: +1}}, + {x: -1, mt: {y: -1}}, + {x: -1, mt: {y: +1}}, ]); // ... when one argument can't be pushed down. @@ -182,28 +182,28 @@ checkAllBucketings({ $or: [ { $and: [ - {'meta.a': {$gt: 0}}, + {'mt.a': {$gt: 0}}, {'x': {$lt: 0}}, ] }, { $and: [ - {'meta.b': {$gte: 0}}, + {'mt.b': {$gte: 0}}, {time: {$gt: ISODate('2020-01-01')}}, ] }, ] }, [ - {meta: {a: -1, b: -1}, x: -1, time: ISODate('2020-02-01')}, - {meta: {a: -1, b: -1}, x: -1, time: ISODate('2019-12-31')}, - {meta: {a: -1, b: -1}, x: +1, time: ISODate('2020-02-01')}, - {meta: {a: -1, b: -1}, x: +1, time: ISODate('2019-12-31')}, + {mt: {a: -1, b: -1}, x: -1, time: ISODate('2020-02-01')}, + {mt: {a: -1, b: -1}, x: -1, time: ISODate('2019-12-31')}, + {mt: {a: -1, b: -1}, x: +1, time: ISODate('2020-02-01')}, + {mt: {a: -1, b: -1}, x: +1, time: ISODate('2019-12-31')}, - {meta: {a: +1, b: -1}, x: -1, time: ISODate('2020-02-01')}, - {meta: {a: +1, b: -1}, x: -1, time: ISODate('2019-12-31')}, - {meta: {a: +1, b: -1}, x: +1, time: ISODate('2020-02-01')}, - {meta: {a: +1, b: -1}, x: +1, time: ISODate('2019-12-31')}, + {mt: {a: +1, b: -1}, x: -1, time: ISODate('2020-02-01')}, + {mt: {a: +1, b: -1}, x: -1, time: ISODate('2019-12-31')}, + {mt: {a: +1, b: -1}, x: +1, time: ISODate('2020-02-01')}, + {mt: {a: +1, b: -1}, x: +1, time: ISODate('2019-12-31')}, ]); // Test nested $and / $or where some leaf predicates cannot be pushed down. @@ -211,72 +211,72 @@ checkAllBucketings({ $or: [ { $and: [ - {'meta.a': {$gt: 0}}, + {'mt.a': {$gt: 0}}, {'x': {$exists: false}}, ] }, { $and: [ - {'meta.b': {$gte: 0}}, + {'mt.b': {$gte: 0}}, {time: {$gt: ISODate('2020-01-01')}}, ] }, ] }, [ - {meta: {a: -1, b: -1}, time: ISODate('2020-02-01')}, - {meta: {a: -1, b: -1}, time: ISODate('2019-12-31')}, - {meta: {a: -1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, - {meta: {a: -1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, + {mt: {a: -1, b: -1}, time: ISODate('2020-02-01')}, + {mt: {a: -1, b: -1}, time: ISODate('2019-12-31')}, + {mt: {a: -1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, + {mt: {a: -1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, - {meta: {a: +1, b: -1}, time: ISODate('2020-02-01')}, - {meta: {a: +1, b: -1}, time: ISODate('2019-12-31')}, - {meta: {a: +1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, - {meta: {a: +1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, + {mt: {a: +1, b: -1}, time: ISODate('2020-02-01')}, + {mt: {a: +1, b: -1}, time: ISODate('2019-12-31')}, + {mt: {a: +1, b: -1}, x: 'asdf', time: ISODate('2020-02-01')}, + {mt: {a: +1, b: -1}, x: 'asdf', time: ISODate('2019-12-31')}, ]); -// Test $exists on meta, inside $or. +// Test $exists on mt, inside $or. checkAllBucketings({ $or: [ - {"meta.a": {$exists: true}}, + {"mt.a": {$exists: true}}, {"x": {$gt: 2}}, ] }, [ - {meta: {a: 1}, x: 1}, - {meta: {a: 2}, x: 2}, - {meta: {a: 3}, x: 3}, - {meta: {a: 4}, x: 4}, - {meta: {}, x: 1}, - {meta: {}, x: 2}, - {meta: {}, x: 3}, - {meta: {}, x: 4}, + {mt: {a: 1}, x: 1}, + {mt: {a: 2}, x: 2}, + {mt: {a: 3}, x: 3}, + {mt: {a: 4}, x: 4}, + {mt: {}, x: 1}, + {mt: {}, x: 2}, + {mt: {}, x: 3}, + {mt: {}, x: 4}, ]); -// Test $in on meta, inside $or. +// Test $in on mt, inside $or. checkAllBucketings({ $or: [ - {"meta.a": {$in: [1, 3]}}, + {"mt.a": {$in: [1, 3]}}, {"x": {$gt: 2}}, ] }, [ - {meta: {a: 1}, x: 1}, - {meta: {a: 2}, x: 2}, - {meta: {a: 3}, x: 3}, - {meta: {a: 4}, x: 4}, - {meta: {}, x: 1}, - {meta: {}, x: 2}, - {meta: {}, x: 3}, - {meta: {}, x: 4}, + {mt: {a: 1}, x: 1}, + {mt: {a: 2}, x: 2}, + {mt: {a: 3}, x: 3}, + {mt: {a: 4}, x: 4}, + {mt: {}, x: 1}, + {mt: {}, x: 2}, + {mt: {}, x: 3}, + {mt: {}, x: 4}, ]); -// Test geo predicates on meta, inside $or. +// Test geo predicates on mt, inside $or. for (const pred of ['$geoWithin', '$geoIntersects']) { checkAllBucketings({ $or: [ { - "meta.location": { + "mt.location": { [pred]: { $geometry: { type: "Polygon", @@ -295,66 +295,90 @@ for (const pred of ['$geoWithin', '$geoIntersects']) { ] }, [ - {meta: {location: [1, 1]}, x: 1}, - {meta: {location: [1, 1]}, x: 2}, - {meta: {location: [1, 1]}, x: 3}, - {meta: {location: [1, 1]}, x: 4}, - {meta: {location: [5, 5]}, x: 1}, - {meta: {location: [5, 5]}, x: 2}, - {meta: {location: [5, 5]}, x: 3}, - {meta: {location: [5, 5]}, x: 4}, + {mt: {location: [1, 1]}, x: 1}, + {mt: {location: [1, 1]}, x: 2}, + {mt: {location: [1, 1]}, x: 3}, + {mt: {location: [1, 1]}, x: 4}, + {mt: {location: [5, 5]}, x: 1}, + {mt: {location: [5, 5]}, x: 2}, + {mt: {location: [5, 5]}, x: 3}, + {mt: {location: [5, 5]}, x: 4}, ]); } -// Test $mod on meta, inside $or. +// Test $mod on mt, inside $or. // $mod is an example of a predicate that we don't handle specially in time-series optimizations: // it can be pushed down if and only if it's on a metadata field. checkAllBucketings({ $or: [ - {"meta.a": {$mod: [2, 0]}}, + {"mt.a": {$mod: [2, 0]}}, {"x": {$gt: 4}}, ] }, [ - {meta: {a: 1}, x: 1}, - {meta: {a: 2}, x: 2}, - {meta: {a: 3}, x: 3}, - {meta: {a: 4}, x: 4}, - {meta: {a: 5}, x: 5}, - {meta: {a: 6}, x: 6}, - {meta: {a: 7}, x: 7}, - {meta: {a: 8}, x: 8}, + {mt: {a: 1}, x: 1}, + {mt: {a: 2}, x: 2}, + {mt: {a: 3}, x: 3}, + {mt: {a: 4}, x: 4}, + {mt: {a: 5}, x: 5}, + {mt: {a: 6}, x: 6}, + {mt: {a: 7}, x: 7}, + {mt: {a: 8}, x: 8}, ]); -// Test $elemMatch on meta, inside $or. +// Test $elemMatch on mt, inside $or. checkAllBucketings({ $or: [ - {"meta.a": {$elemMatch: {b: 3}}}, + {"mt.a": {$elemMatch: {b: 3}}}, {"x": {$gt: 4}}, ] }, [ - {x: 1, meta: {a: []}}, - {x: 2, meta: {a: [{b: 2}]}}, - {x: 3, meta: {a: [{b: 3}]}}, - {x: 4, meta: {a: [{b: 2}, {b: 3}]}}, - {x: 5, meta: {a: []}}, - {x: 6, meta: {a: [{b: 2}]}}, - {x: 7, meta: {a: [{b: 3}]}}, - {x: 8, meta: {a: [{b: 2}, {b: 3}]}}, + {x: 1, mt: {a: []}}, + {x: 2, mt: {a: [{b: 2}]}}, + {x: 3, mt: {a: [{b: 3}]}}, + {x: 4, mt: {a: [{b: 2}, {b: 3}]}}, + {x: 5, mt: {a: []}}, + {x: 6, mt: {a: [{b: 2}]}}, + {x: 7, mt: {a: [{b: 3}]}}, + {x: 8, mt: {a: [{b: 2}, {b: 3}]}}, ]); checkAllBucketings({ $or: [ - {"meta.a": {$elemMatch: {b: 2, c: 3}}}, + {"mt.a": {$elemMatch: {b: 2, c: 3}}}, {"x": {$gt: 3}}, ] }, [ - {x: 1, meta: {a: []}}, - {x: 2, meta: {a: [{b: 2, c: 3}]}}, - {x: 3, meta: {a: [{b: 2}, {c: 3}]}}, - {x: 4, meta: {a: []}}, - {x: 5, meta: {a: [{b: 2, c: 3}]}}, - {x: 6, meta: {a: [{b: 2}, {c: 3}]}}, + {x: 1, mt: {a: []}}, + {x: 2, mt: {a: [{b: 2, c: 3}]}}, + {x: 3, mt: {a: [{b: 2}, {c: 3}]}}, + {x: 4, mt: {a: []}}, + {x: 5, mt: {a: [{b: 2, c: 3}]}}, + {x: 6, mt: {a: [{b: 2}, {c: 3}]}}, ]); + +// Test a standalone $elemMatch on mt. +checkAllBucketings({"mt.a": {$elemMatch: {b: 3}}}, [ + {mt: {a: []}}, + {mt: {a: [{b: 2}]}}, + {mt: {a: [{b: 3}]}}, + {mt: {a: [{b: 2}, {b: 3}]}}, + {mt: {a: []}}, + {mt: {a: [{b: 2}]}}, + {mt: {a: [{b: 3}]}}, + {mt: {a: [{b: 2}, {b: 3}]}}, +]); + +// Test a standalone $size on mt. +checkAllBucketings({"mt.a": {$size: 1}}, [ + {mt: {a: []}}, + {mt: {a: [{b: 2}]}}, + {mt: {a: [{b: 3}]}}, + {mt: {a: [{b: 2}, {b: 3}]}}, + {mt: {a: []}}, + {mt: {a: [{b: 2}]}}, + {mt: {a: [{b: 3}]}}, + {mt: {a: [{b: 2}, {b: 3}]}}, +]); })(); diff --git a/src/mongo/db/exec/bucket_unpacker.cpp b/src/mongo/db/exec/bucket_unpacker.cpp index 1c1a6972101..6ba30b07503 100644 --- a/src/mongo/db/exec/bucket_unpacker.cpp +++ b/src/mongo/db/exec/bucket_unpacker.cpp @@ -616,7 +616,8 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField( // If we have a leaf predicate on a meta field, we can map it to the bucket's meta field. // This includes comparisons such as $eq and $lte, as well as other non-comparison predicates - // such as $exists, $mod, or $elemMatch. + // such as $exists, or $mod. Unrenamable expressions can't be split into a whole bucket level + // filter, when we should return nullptr. // // Metadata predicates are partially handled earlier, by splitting the match expression into a // metadata-only part, and measurement/time-only part. However, splitting a $match into two @@ -634,12 +635,16 @@ BucketSpec::BucketPredicate BucketSpec::createPredicatesOnBucketLevelField( if (!includeMetaField) return handleIneligible(policy, matchExpr, "cannot handle an excluded meta field"); - auto looseResult = matchExpr->shallowClone(); - expression::applyRenamesToExpression( - looseResult.get(), - {{bucketSpec.metaField().value(), timeseries::kBucketMetaFieldName.toString()}}); - auto tightResult = looseResult->shallowClone(); - return {std::move(looseResult), std::move(tightResult)}; + if (expression::hasOnlyRenameableMatchExpressionChildren(*matchExpr)) { + auto looseResult = matchExpr->shallowClone(); + expression::applyRenamesToExpression( + looseResult.get(), + {{bucketSpec.metaField().value(), timeseries::kBucketMetaFieldName.toString()}}); + auto tightResult = looseResult->shallowClone(); + return {std::move(looseResult), std::move(tightResult)}; + } else { + return {nullptr, nullptr}; + } } if (matchExpr->matchType() == MatchExpression::AND) { diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp index 9120800a799..43bbba91eeb 100644 --- a/src/mongo/db/matcher/expression_algo.cpp +++ b/src/mongo/db/matcher/expression_algo.cpp @@ -773,7 +773,6 @@ bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) { return false; } -// Checks if 'expr' has any children which do not have renaming implemented. bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr) { if (expr.matchType() == MatchExpression::MatchType::EXPRESSION) { return true; diff --git a/src/mongo/db/matcher/expression_algo.h b/src/mongo/db/matcher/expression_algo.h index f43957fe80c..74300a724ca 100644 --- a/src/mongo/db/matcher/expression_algo.h +++ b/src/mongo/db/matcher/expression_algo.h @@ -54,6 +54,11 @@ using NodeTraversalFunc = std::function<void(MatchExpression*, std::string)>; bool hasExistencePredicateOnPath(const MatchExpression& expr, StringData path); /** + * Checks if 'expr' has any children which do not have renaming implemented. + */ +bool hasOnlyRenameableMatchExpressionChildren(const MatchExpression& expr); + +/** * Returns true if the documents matched by 'lhs' are a subset of the documents matched by * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. * @@ -183,6 +188,8 @@ splitMatchExpressionBy(std::unique_ptr<MatchExpression> expr, * to the new values of those paths. For example, suppose the original match expression is * {old: {$gt: 3}} and 'renames' contains the mapping "old" => "new". At the end, 'expr' will be * {new: {$gt: 3}}. + * + * The caller should make sure that `expr` is renamable as a whole. */ void applyRenamesToExpression(MatchExpression* expr, const StringMap<std::string>& renames); |