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-10 03:38:37 +0000
commit02eaef3b79524560ff4d651255e1ae3f280f5bcd (patch)
tree29cdf4867b13ec2e3946ade2eb6942e1b790120c
parent1e5c67d6b9c80eda789fece2e1973a7f7c6cb7b0 (diff)
downloadmongo-02eaef3b79524560ff4d651255e1ae3f280f5bcd.tar.gz
SERVER-73697 Do not split out match expression on meta field when it's not renameable
-rw-r--r--etc/backports_required_for_multiversion_tests.yml4
-rw-r--r--jstests/core/timeseries/timeseries_predicates.js204
-rw-r--r--src/mongo/db/exec/bucket_unpacker.cpp19
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp1
-rw-r--r--src/mongo/db/matcher/expression_algo.h7
5 files changed, 137 insertions, 98 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index fe00e4762c1..0fccbb28aee 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -308,6 +308,8 @@ last-continuous:
ticket: SERVER-73110
- test_file: jstests/replsets/quiesce_mode_fails_elections.js
ticket: SERVER-72774
+ - test_file: jstests/core/timeseries/timeseries_predicates.js
+ ticket: SERVER-73697
suites: null
last-lts:
all:
@@ -691,4 +693,6 @@ last-lts:
ticket: SERVER-73110
- test_file: jstests/replsets/quiesce_mode_fails_elections.js
ticket: SERVER-72774
+ - test_file: jstests/core/timeseries/timeseries_predicates.js
+ ticket: SERVER-73697
suites: null
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);