summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/query_shape/find_cmd_shape_test.cpp
diff options
context:
space:
mode:
authorLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
committerLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
commit959575a5ca598bf5f37fb5cebe7ed1d80d3d71f7 (patch)
treeacc8d60aedb12b70048e676e8a7349deb0010db8 /src/mongo/db/query/query_shape/find_cmd_shape_test.cpp
parent76588293975fc059cf076779e4283e6ffaf8afff (diff)
New upstream version 6.0.20upstream
Diffstat (limited to 'src/mongo/db/query/query_shape/find_cmd_shape_test.cpp')
-rw-r--r--src/mongo/db/query/query_shape/find_cmd_shape_test.cpp238
1 files changed, 238 insertions, 0 deletions
diff --git a/src/mongo/db/query/query_shape/find_cmd_shape_test.cpp b/src/mongo/db/query/query_shape/find_cmd_shape_test.cpp
new file mode 100644
index 00000000000..0d839a5d3d2
--- /dev/null
+++ b/src/mongo/db/query/query_shape/find_cmd_shape_test.cpp
@@ -0,0 +1,238 @@
+/**
+ * Copyright (C) 2023-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/db/pipeline/expression_context_for_test.h"
+#include "mongo/db/query/query_shape/find_cmd_shape.h"
+#include "mongo/db/service_context_test_fixture.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo::query_shape {
+
+namespace {
+/**
+ * TODO this was stolen from another test. Time for a library?
+ * Simplistic redaction strategy for testing which appends the field name to the prefix "REDACT_".
+ */
+std::string applyHmacForTest(StringData sd) {
+ return "REDACT_" + sd.toString();
+}
+
+static const NamespaceStringOrUUID kDefaultTestNss =
+ NamespaceStringOrUUID{NamespaceString("testDB.testColl")};
+
+struct RequestOptions {
+ OptionalBool singleBatch = {};
+ OptionalBool allowDiskUse = {};
+ OptionalBool returnKey = {};
+ OptionalBool showRecordId = {};
+ OptionalBool tailable = {};
+ OptionalBool awaitData = {};
+ OptionalBool limit = {};
+ OptionalBool skip = {};
+};
+class FindCmdShapeTest : public ServiceContextTest {
+public:
+ void setUp() final {
+ _expCtx = make_intrusive<ExpressionContextForTest>();
+ }
+
+ std::unique_ptr<FindCmdShape> makeShapeFromSort(StringData sortJson) {
+ auto fcr = std::make_unique<FindCommandRequest>(kDefaultTestNss);
+ fcr->setSort(fromjson(sortJson.rawData()));
+ auto&& parsedRequest =
+ uassertStatusOK(::mongo::parsed_find_command::parse(_expCtx, std::move(fcr)));
+ return std::make_unique<FindCmdShape>(*parsedRequest, _expCtx);
+ }
+
+ BSONObj sortShape(StringData sortJson) {
+ auto shape = makeShapeFromSort(sortJson);
+ return shape->components.sort;
+ }
+
+ /**
+ * Returns the shape of the input sort, or boost::none if the input shape was a natural sort
+ * which got converted into a hint.
+ */
+ boost::optional<BSONObj> maybeRedactedSortShape(StringData sortJson) {
+ auto shape = makeShapeFromSort(sortJson);
+ SerializationOptions opts = SerializationOptions::kDebugQueryShapeSerializeOptions;
+ opts.transformIdentifiers = true;
+ opts.transformIdentifiersCallback = applyHmacForTest;
+ auto shapeBson = shape->toBson(_expCtx->opCtx, opts);
+ if (auto sortElem = shapeBson["sort"]; !sortElem.eoo()) {
+ return sortElem.Obj().getOwned();
+ }
+ return boost::none;
+ }
+
+ BSONObj redactedSortShape(StringData sortJson) {
+ return *maybeRedactedSortShape(sortJson);
+ }
+
+ boost::intrusive_ptr<ExpressionContext> _expCtx;
+
+ std::unique_ptr<FindCmdShapeComponents> makeShapeComponentsFromFilter(
+ BSONObj filter, const RequestOptions& requestOptions = {}) {
+ auto fcr = std::make_unique<FindCommandRequest>(kDefaultTestNss);
+ fcr->setFilter(filter.getOwned());
+ fcr->setSingleBatch(requestOptions.singleBatch);
+ fcr->setAllowDiskUse(requestOptions.allowDiskUse);
+ fcr->setReturnKey(requestOptions.returnKey);
+ fcr->setAllowDiskUse(requestOptions.showRecordId);
+ fcr->setTailable(requestOptions.tailable);
+ fcr->setAwaitData(requestOptions.awaitData);
+ auto parsedFind = uassertStatusOK(parsed_find_command::parse(_expCtx, {std::move(fcr)}));
+ return std::make_unique<FindCmdShapeComponents>(*parsedFind, _expCtx);
+ }
+
+ std::unique_ptr<FindCmdShape> makeShapeFromFilter(const BSONObj& filter) {
+ auto fcr = std::make_unique<FindCommandRequest>(kDefaultTestNss);
+ fcr->setFilter(filter.getOwned());
+ auto parsedFind = uassertStatusOK(parsed_find_command::parse(_expCtx, {std::move(fcr)}));
+ return std::make_unique<FindCmdShape>(*parsedFind, _expCtx);
+ }
+};
+
+TEST_F(FindCmdShapeTest, NormalSortPattern) {
+ ASSERT_BSONOBJ_EQ_AUTO( // NOLINT
+ R"({"a.b.c":1,"foo":-1})",
+ sortShape(R"({"a.b.c": 1, "foo": -1})"));
+}
+
+TEST_F(FindCmdShapeTest, NaturalSortPattern) {
+ // $natural sorts are interpreted as a hint. Hints are not part of the shape (but should show up
+ // in the query stats key).
+ ASSERT_BSONOBJ_EQ_AUTO( // NOLINT
+ R"({})",
+ sortShape(R"({$natural: 1})"));
+ ASSERT_BSONOBJ_EQ_AUTO( // NOLINT
+ R"({})",
+ sortShape(R"({$natural: -1})"));
+}
+
+TEST_F(FindCmdShapeTest, NaturalSortPatternWithMeta) {
+ ASSERT_THROWS_CODE(
+ sortShape(R"({$natural: 1, x: {$meta: "textScore"}})"), DBException, ErrorCodes::BadValue);
+}
+
+TEST_F(FindCmdShapeTest, MetaPatternWithoutNatural) {
+ ASSERT_BSONOBJ_EQ_AUTO( // NOLINT
+ R"({"normal":1,"$computed1":{"$meta":"textScore"}})",
+ sortShape(R"({normal: 1, x: {$meta: "textScore"}})"));
+}
+
+// Here we have one test to ensure that the redaction policy is accepted and applied in the
+// query_shape utility, but there are more extensive redaction tests in sort_pattern_test.cpp
+TEST_F(FindCmdShapeTest, RespectsRedactionPolicy) {
+ ASSERT_BSONOBJ_EQ_AUTO( // NOLINT
+ R"({"REDACT_normal":1,"REDACT_y":1})",
+ redactedSortShape(R"({normal: 1, y: 1})"));
+
+ // No need to redact $natural. Again, this will be interpreted as a hint, but this test is
+ // interesting to ensure the $-prefix of $natural doesn't confuse us.
+ ASSERT(!maybeRedactedSortShape(R"({$natural: 1})"));
+}
+
+TEST_F(FindCmdShapeTest, SizeOfShapeComponents) {
+ auto query = BSON("query" << 1 << "xEquals" << 42);
+ auto findCmdComponent = makeShapeComponentsFromFilter(query.getOwned());
+ const auto querySize = findCmdComponent->filter.objsize();
+
+ const auto minimumSize = sizeof(FindCmdShapeComponents) + querySize;
+ ASSERT_GT(findCmdComponent->size(), minimumSize);
+ ASSERT_LTE(findCmdComponent->size(),
+ minimumSize + static_cast<size_t>(4 * BSONObj().objsize()));
+}
+
+TEST_F(FindCmdShapeTest, EquivalentShapeComponentsSizes) {
+ auto query = BSON("query" << 1 << "xEquals" << 42);
+ // Tailable can not be set together with 'singleBatch' option.
+ auto mostlyTrueComponent = makeShapeComponentsFromFilter(query.getOwned(),
+ {/* singleBatch = */ false,
+ /* allowDiskUse = */ true,
+ /* returnKey = */ true,
+ /* showRecordId = */ true,
+ /* tailable = */ true,
+ /* awaitData = */ true,
+ /* limit = */ true,
+ /* skip = */ true});
+
+ auto mostlyFalseComponent = makeShapeComponentsFromFilter(query.getOwned(),
+ {/* singleBatch = */ false,
+ /* allowDiskUse = */ false,
+ /* returnKey = */ false,
+ /* showRecordId = */ false,
+ /* tailable = */ true,
+ /* awaitData = */ false,
+ /* limit = */ false,
+ /* skip = */ false});
+
+ ASSERT_EQ(mostlyTrueComponent->size(), mostlyFalseComponent->size());
+}
+
+TEST_F(FindCmdShapeTest, DifferentShapeComponentsSizes) {
+ auto smallQuery = BSON("query" << BSONObj());
+ auto smallFindCmdComponent = makeShapeComponentsFromFilter(smallQuery.getOwned());
+
+ auto largeQuery = BSON("query" << 1 << "xEquals" << 42);
+ auto largeFindCmdComponent = makeShapeComponentsFromFilter(largeQuery.getOwned());
+
+ ASSERT_LT(smallQuery.objsize(), largeQuery.objsize());
+ ASSERT_LT(smallFindCmdComponent->size(), largeFindCmdComponent->size());
+}
+
+TEST_F(FindCmdShapeTest, SizeOfShapeWithAndWithoutLet) {
+ auto filter = BSON("query" << 1 << "xEquals" << 42);
+ auto shapeWithoutLet = makeShapeFromFilter(filter.getOwned());
+
+ auto fcr = std::make_unique<FindCommandRequest>(kDefaultTestNss);
+ fcr->setFilter(filter.getOwned());
+ fcr->setLet(fromjson(R"({x: 4})"));
+ auto parsedFind = uassertStatusOK(parsed_find_command::parse(_expCtx, {std::move(fcr)}));
+ auto shapeWithLet = std::make_unique<FindCmdShape>(*parsedFind, _expCtx);
+
+ ASSERT_LT(shapeWithoutLet->size(), shapeWithLet->size());
+}
+
+TEST_F(FindCmdShapeTest, SizeOfShapeWithAndWithoutCollation) {
+ auto filter = BSON("query" << 1 << "xEquals" << 42);
+ auto shapeWithoutCollation = makeShapeFromFilter(filter.getOwned());
+
+ auto fcr = std::make_unique<FindCommandRequest>(kDefaultTestNss);
+ fcr->setFilter(filter.getOwned());
+ fcr->setCollation(fromjson(R"({locale: "en_US"})"));
+ auto parsedFind = uassertStatusOK(parsed_find_command::parse(_expCtx, {std::move(fcr)}));
+ auto shapeWithCollation = std::make_unique<FindCmdShape>(*parsedFind, _expCtx);
+
+ ASSERT_LT(shapeWithoutCollation->size(), shapeWithCollation->size());
+}
+
+} // namespace
+
+} // namespace mongo::query_shape