summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYoonsoo Kim <yoonsoo.kim@mongodb.com>2023-02-10 00:59:14 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2023-02-21 22:40:40 +0000
commit72bf5680cbba28b5f2c909459093fba6c9f348d4 (patch)
treeba2d232e06bde0c2ffb18c296ddb056a142caff0
parentb49764624326b413765199ba089b652ba5c7be5a (diff)
downloadmongo-72bf5680cbba28b5f2c909459093fba6c9f348d4.tar.gz
SERVER-73697 Do not split out match expression on meta field when it's not renameable
(cherry picked from commit 02eaef3b79524560ff4d651255e1ae3f280f5bcd)
-rw-r--r--jstests/core/timeseries/timeseries_predicates.js204
-rw-r--r--src/mongo/db/exec/bucket_unpacker.cpp17
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp1
-rw-r--r--src/mongo/db/matcher/expression_algo.h7
4 files changed, 132 insertions, 97 deletions
diff --git a/jstests/core/timeseries/timeseries_predicates.js b/jstests/core/timeseries/timeseries_predicates.js
index a273cc8b128..ca78464cb31 100644
--- a/jstests/core/timeseries/timeseries_predicates.js
+++ b/jstests/core/timeseries/timeseries_predicates.js
@@ -17,7 +17,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,
@@ -86,18 +86,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.
@@ -131,18 +131,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.
@@ -180,28 +180,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.
@@ -209,72 +209,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",
@@ -293,66 +293,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 4aa38bba785..b078941f677 100644
--- a/src/mongo/db/exec/bucket_unpacker.cpp
+++ b/src/mongo/db/exec/bucket_unpacker.cpp
@@ -488,7 +488,8 @@ std::unique_ptr<MatchExpression> 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
@@ -506,11 +507,15 @@ std::unique_ptr<MatchExpression> BucketSpec::createPredicatesOnBucketLevelField(
if (!includeMetaField)
return handleIneligible(policy, matchExpr, "cannot handle an excluded meta field");
- auto result = matchExpr->shallowClone();
- expression::applyRenamesToExpression(
- result.get(),
- {{bucketSpec.metaField().get(), timeseries::kBucketMetaFieldName.toString()}});
- return result;
+ if (expression::hasOnlyRenameableMatchExpressionChildren(*matchExpr)) {
+ auto result = matchExpr->shallowClone();
+ expression::applyRenamesToExpression(
+ result.get(),
+ {{bucketSpec.metaField().value(), timeseries::kBucketMetaFieldName.toString()}});
+ return result;
+ } else {
+ return 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 0e67713864a..558ef533380 100644
--- a/src/mongo/db/matcher/expression_algo.cpp
+++ b/src/mongo/db/matcher/expression_algo.cpp
@@ -770,7 +770,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 70c7e0adcc6..fc42fca7814 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.
*
@@ -170,6 +175,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);