summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/semantic_analysis_test.cpp
diff options
context:
space:
mode:
authorGeorge Wangensteen <george.wangensteen@10gen.com>2019-06-19 15:24:38 -0400
committerGeorge Wangensteen <george.wangensteen@10gen.com>2019-06-28 16:01:59 -0400
commitb651ed3dc6ae61332bb3193afef1cfa41bf5df53 (patch)
treed2fd427998d5ec995aeaa4faf8d13e597e745aa5 /src/mongo/db/pipeline/semantic_analysis_test.cpp
parentabe43b7aaddd8254c51eb2c8bfce99b9e11f8fee (diff)
downloadmongo-b651ed3dc6ae61332bb3193afef1cfa41bf5df53.tar.gz
SERVER-41750 refactor renamed paths API.
Diffstat (limited to 'src/mongo/db/pipeline/semantic_analysis_test.cpp')
-rw-r--r--src/mongo/db/pipeline/semantic_analysis_test.cpp434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/semantic_analysis_test.cpp b/src/mongo/db/pipeline/semantic_analysis_test.cpp
new file mode 100644
index 00000000000..8b43af7e9c3
--- /dev/null
+++ b/src/mongo/db/pipeline/semantic_analysis_test.cpp
@@ -0,0 +1,434 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/pipeline/document_source.h"
+#include "mongo/db/pipeline/document_source_test_optimizations.h"
+#include "mongo/db/pipeline/semantic_analysis.h"
+#include "mongo/db/service_context_test_fixture.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+namespace {
+
+using namespace semantic_analysis;
+
+class RenamesAToB : public DocumentSourceTestOptimizations {
+public:
+ RenamesAToB() : DocumentSourceTestOptimizations() {}
+ GetModPathsReturn getModifiedPaths() const final {
+ // Pretend this stage simply renames the "a" field to be "b", leaving the value of "a" the
+ // same. This would be the equivalent of an {$addFields: {b: "$a"}}.
+ return {GetModPathsReturn::Type::kFiniteSet, std::set<std::string>{}, {{"b", "a"}}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, DoesReturnSimpleRenameFromFiniteSetRename) {
+ RenamesAToB renamesAToB;
+ {
+ auto renames = renamedPaths({"a"}, renamesAToB, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["a"], "b");
+ }
+ {
+ auto renames = renamedPaths({"b"}, renamesAToB, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["b"], "a");
+ }
+}
+
+TEST(SemanticAnalysisRenamedPaths, ReturnsSimpleMapForUnaffectedFieldsFromFiniteSetRename) {
+ RenamesAToB renamesAToB;
+ {
+ auto renames = renamedPaths({"c"}, renamesAToB, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["c"], "c");
+ }
+ {
+ auto renames = renamedPaths({"c"}, renamesAToB, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["c"], "c");
+ }
+ {
+ auto renames = renamedPaths({"a"}, renamesAToB, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["a"], "b");
+ }
+ {
+ auto renames = renamedPaths({"b"}, renamesAToB, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["b"], "a");
+ }
+ {
+ auto renames = renamedPaths({"e", "f", "g"}, renamesAToB, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 3UL);
+ ASSERT_EQ(map["e"], "e");
+ ASSERT_EQ(map["f"], "f");
+ ASSERT_EQ(map["g"], "g");
+ }
+ {
+ auto renames = renamedPaths({"e", "f", "g"}, renamesAToB, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 3UL);
+ ASSERT_EQ(map["e"], "e");
+ ASSERT_EQ(map["f"], "f");
+ ASSERT_EQ(map["g"], "g");
+ }
+}
+
+class RenameCToDPreserveEFG : public DocumentSourceTestOptimizations {
+public:
+ RenameCToDPreserveEFG() : DocumentSourceTestOptimizations() {}
+
+ GetModPathsReturn getModifiedPaths() const final {
+ return {GetModPathsReturn::Type::kAllExcept,
+ std::set<std::string>{"e", "f", "g"},
+ {{"d", "c"}}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, DoesReturnSimpleRenameFromAllExceptRename) {
+ RenameCToDPreserveEFG renameCToDPreserveEFG;
+ {
+ auto renames = renamedPaths({"c"}, renameCToDPreserveEFG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["c"], "d");
+ }
+ {
+ auto renames = renamedPaths({"d"}, renameCToDPreserveEFG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["d"], "c");
+ }
+}
+
+TEST(SemanticAnalysisRenamedPaths, ReturnsSimpleMapForUnaffectedFieldsFromAllExceptRename) {
+ RenameCToDPreserveEFG renameCToDPreserveEFG;
+ {
+ auto renames = renamedPaths({"e"}, renameCToDPreserveEFG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["e"], "e");
+ }
+ {
+ auto renames = renamedPaths({"e"}, renameCToDPreserveEFG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["e"], "e");
+ }
+ {
+ auto renames = renamedPaths({"f", "g"}, renameCToDPreserveEFG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["f"], "f");
+ ASSERT_EQ(map["g"], "g");
+ }
+ {
+ auto renames = renamedPaths({"f", "g"}, renameCToDPreserveEFG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["f"], "f");
+ ASSERT_EQ(map["g"], "g");
+ }
+}
+
+class RenameCDotDToEPreserveFDotG : public DocumentSourceTestOptimizations {
+public:
+ RenameCDotDToEPreserveFDotG() : DocumentSourceTestOptimizations() {}
+
+ GetModPathsReturn getModifiedPaths() const final {
+ return {GetModPathsReturn::Type::kAllExcept, std::set<std::string>{"f.g"}, {{"e", "c.d"}}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, DoesReturnRenameToDottedFieldFromAllExceptRename) {
+ RenameCDotDToEPreserveFDotG renameCDotDToEPreserveFDotG;
+ {
+ auto renames = renamedPaths({"c.d"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["c.d"], "e");
+ }
+ {
+ auto renames = renamedPaths({"e"}, renameCDotDToEPreserveFDotG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["e"], "c.d");
+ }
+ {
+ auto renames =
+ renamedPaths({"c.d.x", "c.d.y"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["c.d.x"], "e.x");
+ ASSERT_EQ(map["c.d.y"], "e.y");
+ }
+ {
+ auto renames =
+ renamedPaths({"e.x", "e.y"}, renameCDotDToEPreserveFDotG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["e.x"], "c.d.x");
+ ASSERT_EQ(map["e.y"], "c.d.y");
+ }
+}
+
+TEST(SemanticAnalysisRenamedPaths,
+ DoesNotTreatPrefixAsUnmodifiedWhenSuffixIsModifiedFromAllExcept) {
+ RenameCDotDToEPreserveFDotG renameCDotDToEPreserveFDotG;
+ {
+ auto renames = renamedPaths({"f"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"f"}, renameCDotDToEPreserveFDotG, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ // This is the exception, the only path that is not modified.
+ auto renames = renamedPaths({"f.g"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["f.g"], "f.g");
+ }
+ {
+ // This is the exception, the only path that is not modified.
+ auto renames = renamedPaths({"f.g"}, renameCDotDToEPreserveFDotG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["f.g"], "f.g");
+ }
+ {
+ // We know "f.g" is preserved, so it follows that a subpath of that path is also preserved.
+ auto renames = renamedPaths(
+ {"f.g.x", "f.g.xyz.foobarbaz"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["f.g.x"], "f.g.x");
+ ASSERT_EQ(map["f.g.xyz.foobarbaz"], "f.g.xyz.foobarbaz");
+ }
+ {
+ auto renames = renamedPaths(
+ {"f.g.x", "f.g.xyz.foobarbaz"}, renameCDotDToEPreserveFDotG, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["f.g.x"], "f.g.x");
+ ASSERT_EQ(map["f.g.xyz.foobarbaz"], "f.g.xyz.foobarbaz");
+ }
+ {
+ // This shares a prefix with the unmodified path, but should not be reported as unmodified.
+ auto renames = renamedPaths({"f.x"}, renameCDotDToEPreserveFDotG, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+}
+
+class RenameAToXDotYModifyCDotD : public DocumentSourceTestOptimizations {
+public:
+ RenameAToXDotYModifyCDotD() : DocumentSourceTestOptimizations() {}
+
+ GetModPathsReturn getModifiedPaths() const final {
+ return {GetModPathsReturn::Type::kFiniteSet, std::set<std::string>{"c.d"}, {{"x.y", "a"}}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, DoesReturnRenameToDottedFieldFromFiniteSetRename) {
+ RenameAToXDotYModifyCDotD renameAToXDotYModifyCDotD;
+ {
+ auto renames = renamedPaths({"a"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["a"], "x.y");
+ }
+ {
+ auto renames = renamedPaths({"x.y"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 1UL);
+ ASSERT_EQ(map["x.y"], "a");
+ }
+ {
+ auto renames =
+ renamedPaths({"a.z", "a.a.b.c"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["a.z"], "x.y.z");
+ ASSERT_EQ(map["a.a.b.c"], "x.y.a.b.c");
+ }
+ {
+ auto renames =
+ renamedPaths({"x.y.z", "x.y.a.b.c"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["x.y.z"], "a.z");
+ ASSERT_EQ(map["x.y.a.b.c"], "a.a.b.c");
+ }
+}
+
+TEST(SemanticAnalysisRenamedPaths, DoesNotTreatPrefixAsUnmodifiedWhenSuffixIsPartOfModifiedSet) {
+ RenameAToXDotYModifyCDotD renameAToXDotYModifyCDotD;
+ {
+ auto renames = renamedPaths({"c"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"c.d"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"c.d.e"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"c"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"c.d"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"c.d.e"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames =
+ renamedPaths({"c.not_d", "c.decoy"}, renameAToXDotYModifyCDotD, Direction::kForward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["c.not_d"], "c.not_d");
+ ASSERT_EQ(map["c.decoy"], "c.decoy");
+ }
+ {
+ auto renames =
+ renamedPaths({"c.not_d", "c.decoy"}, renameAToXDotYModifyCDotD, Direction::kBackward);
+ ASSERT(static_cast<bool>(renames));
+ auto map = *renames;
+ ASSERT_EQ(map.size(), 2UL);
+ ASSERT_EQ(map["c.not_d"], "c.not_d");
+ ASSERT_EQ(map["c.decoy"], "c.decoy");
+ }
+}
+
+class ModifiesAllPaths : public DocumentSourceTestOptimizations {
+public:
+ ModifiesAllPaths() : DocumentSourceTestOptimizations() {}
+ GetModPathsReturn getModifiedPaths() const final {
+ return {GetModPathsReturn::Type::kAllPaths, std::set<std::string>{}, {}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, ReturnsNoneWhenAllPathsAreModified) {
+ ModifiesAllPaths modifiesAllPaths;
+ {
+ auto renames = renamedPaths({"a"}, modifiesAllPaths, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a", "b", "c.d"}, modifiesAllPaths, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a"}, modifiesAllPaths, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a", "b", "c.d"}, modifiesAllPaths, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+}
+
+class ModificationsUnknown : public DocumentSourceTestOptimizations {
+public:
+ ModificationsUnknown() : DocumentSourceTestOptimizations() {}
+ GetModPathsReturn getModifiedPaths() const final {
+ return {GetModPathsReturn::Type::kNotSupported, std::set<std::string>{}, {}};
+ }
+};
+
+TEST(SemanticAnalysisRenamedPaths, ReturnsNoneWhenModificationsAreNotKnown) {
+ ModificationsUnknown modificationsUnknown;
+ {
+ auto renames = renamedPaths({"a"}, modificationsUnknown, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a", "b", "c.d"}, modificationsUnknown, Direction::kForward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a"}, modificationsUnknown, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+ {
+ auto renames = renamedPaths({"a", "b", "c.d"}, modificationsUnknown, Direction::kBackward);
+ ASSERT_FALSE(static_cast<bool>(renames));
+ }
+}
+
+} // namespace
+} // namespace mongo