summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/expression_date_test.cpp
diff options
context:
space:
mode:
authorMindaugas Malinauskas <mindaugas.malinauskas@mongodb.com>2020-10-30 14:17:12 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-30 16:03:39 +0000
commitf0893e135f9c0dcc68e5189756dc3299674cfb35 (patch)
tree5a8565e100826fa7a3125a5017ad9fdb2f2951c2 /src/mongo/db/pipeline/expression_date_test.cpp
parente5e5ca95d5241710392e7bd4b0d58486843366f9 (diff)
downloadmongo-f0893e135f9c0dcc68e5189756dc3299674cfb35.tar.gz
SERVER-51663 Implement $dateDiff in DocumentSource execution
Diffstat (limited to 'src/mongo/db/pipeline/expression_date_test.cpp')
-rw-r--r--src/mongo/db/pipeline/expression_date_test.cpp283
1 files changed, 283 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/expression_date_test.cpp b/src/mongo/db/pipeline/expression_date_test.cpp
index 3098eec3f5a..ba0b3552188 100644
--- a/src/mongo/db/pipeline/expression_date_test.cpp
+++ b/src/mongo/db/pipeline/expression_date_test.cpp
@@ -29,6 +29,9 @@
#include "mongo/platform/basic.h"
+#include <set>
+#include <string>
+
#include "mongo/db/exec/document_value/document_value_test_util.h"
#include "mongo/db/pipeline/aggregation_context_fixture.h"
#include "mongo/unittest/unittest.h"
@@ -1398,4 +1401,284 @@ TEST_F(ExpressionDateFromStringTest, OnErrorEvaluatedLazily) {
}
} // namespace ExpressionDateFromStringTest
+
+namespace ExpressionDateDiffTest {
+class ExpressionDateDiffTest : public AggregationContextFixture {
+public:
+ /**
+ * Parses expression 'expression' and asserts that the expression fails to parse with error
+ * 'expectedErrorCode' and exception message 'expectedErrorMessage'.
+ */
+ void assertFailsToParseExpression(BSONObj expression,
+ int expectedErrorCode,
+ std::string expectedErrorMessage) {
+ auto expCtx = getExpCtx();
+ ASSERT_THROWS_CODE_AND_WHAT(
+ Expression::parseExpression(expCtx.get(), expression, expCtx->variablesParseState),
+ AssertionException,
+ expectedErrorCode,
+ expectedErrorMessage);
+ }
+
+ /**
+ * Parses expression 'expression' which is expected to parse successfully and then serializes
+ * expression instance to compare with 'expectedSerializedExpression'.
+ */
+ void assertParsesAndSerializesExpression(BSONObj expression,
+ BSONObj expectedSerializedExpression) {
+ auto expCtx = getExpCtx();
+ auto dateDiffExpr =
+ Expression::parseExpression(expCtx.get(), expression, expCtx->variablesParseState);
+ auto expectedSerialization = Value(expectedSerializedExpression);
+ ASSERT_VALUE_EQ(dateDiffExpr->serialize(true), expectedSerialization);
+ ASSERT_VALUE_EQ(dateDiffExpr->serialize(false), expectedSerialization);
+
+ // Verify that parsed and then serialized expression is the same.
+ ASSERT_VALUE_EQ(Expression::parseExpression(
+ expCtx.get(), expectedSerializedExpression, expCtx->variablesParseState)
+ ->serialize(false),
+ expectedSerialization);
+ }
+
+ /**
+ * Builds a $dateDiff expression with given values of parameters.
+ */
+ auto buildExpressionWithParameters(Value startDate, Value endDate, Value unit, Value timezone) {
+ auto expCtx = getExpCtx();
+ auto expression =
+ BSON("$dateDiff" << BSON("startDate" << startDate << "endDate" << endDate << "unit"
+ << unit << "timezone" << timezone));
+ return Expression::parseExpression(expCtx.get(), expression, expCtx->variablesParseState);
+ }
+};
+
+TEST_F(ExpressionDateDiffTest, ParsesAndSerializesValidExpression) {
+ assertParsesAndSerializesExpression(BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField"
+ << "unit"
+ << "day"
+ << "timezone"
+ << "America/New_York")),
+ BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField"
+ << "unit"
+ << BSON("$const"
+ << "day")
+ << "timezone"
+ << BSON("$const"
+ << "America/New_York"))));
+ assertParsesAndSerializesExpression(BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField"
+ << "unit"
+ << "$unit")),
+ BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField"
+ << "unit"
+ << "$unit")));
+}
+
+TEST_F(ExpressionDateDiffTest, ParsesInvalidExpression) {
+ // Verify that invalid fields are rejected.
+ assertFailsToParseExpression(BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField"
+ << "unit"
+ << "day"
+ << "timeGone"
+ << "yes")),
+ 5166302,
+ "Unrecognized argument to $dateDiff: timeGone");
+
+ // Verify that field 'startDate' is required.
+ assertFailsToParseExpression(BSON("$dateDiff" << BSON("endDate"
+ << "$endDateField"
+ << "unit"
+ << "day")),
+ 5166303,
+ "Missing 'startDate' parameter to $dateDiff");
+
+ // Verify that field 'endDate' is required.
+ assertFailsToParseExpression(BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "unit"
+ << "day")),
+ 5166304,
+ "Missing 'endDate' parameter to $dateDiff");
+
+ // Verify that field 'unit' is required.
+ assertFailsToParseExpression(BSON("$dateDiff" << BSON("startDate"
+ << "$startDateField"
+ << "endDate"
+ << "$endDateField")),
+ 5166305,
+ "Missing 'unit' parameter to $dateDiff");
+
+ // Verify that only $dateDiff: {..} is accepted.
+ assertFailsToParseExpression(BSON("$dateDiff"
+ << "startDate"),
+ 5166301,
+ "$dateDiff only supports an object as its argument");
+}
+
+TEST_F(ExpressionDateDiffTest, EvaluatesExpression) {
+ struct TestCase {
+ Value startDate;
+ Value endDate;
+ Value unit;
+ Value timezone;
+ Value expectedResult;
+ int expectedErrorCode{0};
+ std::string expectedErrorMessage;
+ };
+ auto expCtx = getExpCtx();
+ const auto anyDate = Value{Date_t{}};
+ const auto null = Value{BSONNULL};
+ const auto hour = Value{"hour"_sd};
+ const auto utc = Value{"GMT"_sd};
+ const auto objectId = Value{OID::gen()};
+ const std::vector<TestCase> testCases{
+ {// Sunny day case.
+ Value{Date_t::fromMillisSinceEpoch(1604255016000) /* 2020-11-01T18:23:36 UTC+00:00 */},
+ Value{Date_t::fromMillisSinceEpoch(1604260800000) /* 2020-11-01T20:00:00 UTC+00:00 */},
+ hour,
+ utc,
+ Value{2}},
+ {// 'startDate' is null.
+ null,
+ anyDate,
+ hour,
+ utc,
+ null},
+ {// 'endDate' is null.
+ anyDate,
+ null,
+ hour,
+ utc,
+ null},
+ {// 'unit' is null.
+ anyDate,
+ anyDate,
+ null,
+ utc,
+ null},
+ {// Invalid 'startDate' type.
+ Value{"date"_sd},
+ anyDate,
+ hour,
+ utc,
+ null,
+ 5166307, // Error code.
+ "$dateDiff requires 'startDate' to be a date, but got string"},
+ {// Invalid 'endDate' type.
+ anyDate,
+ Value{"date"_sd},
+ hour,
+ utc,
+ null,
+ 5166307, // Error code.
+ "$dateDiff requires 'endDate' to be a date, but got string"},
+ {// Invalid 'unit' type.
+ anyDate,
+ anyDate,
+ Value{2},
+ utc,
+ null,
+ 5166306, // Error code.
+ "$dateDiff requires 'unit' to be a string, but got int"},
+ {// Invalid 'unit' value.
+ anyDate,
+ anyDate,
+ Value{"century"_sd},
+ utc,
+ null,
+ ErrorCodes::FailedToParse, // Error code.
+ "$dateDiff parameter 'unit' value parsing failed :: caused by :: unknown time unit value: "
+ "century"},
+ {// Invalid 'timezone' value.
+ anyDate,
+ anyDate,
+ hour,
+ Value{"INVALID"_sd},
+ null,
+ 40485, // Error code.
+ "$dateDiff parameter 'timezone' value parsing failed :: caused by :: unrecognized time "
+ "zone identifier: \"INVALID\""},
+ {// Accepts OID.
+ objectId,
+ objectId,
+ hour,
+ utc,
+ Value{0}},
+ {// Accepts timestamp.
+ Value{Timestamp{Seconds(1604255016), 0} /* 2020-11-01T18:23:36 UTC+00:00 */},
+ Value{Timestamp{Seconds(1604260800), 0} /* 2020-11-01T20:00:00 UTC+00:00 */},
+ Value{"minute"_sd},
+ Value{} /* 'timezone' not specified*/,
+ Value{97}}};
+
+ for (auto&& testCase : testCases) {
+ auto dateDiffExpression = buildExpressionWithParameters(
+ testCase.startDate, testCase.endDate, testCase.unit, testCase.timezone);
+ if (testCase.expectedErrorCode) {
+ ASSERT_THROWS_CODE_AND_WHAT(dateDiffExpression->evaluate({}, &(expCtx->variables)),
+ AssertionException,
+ testCase.expectedErrorCode,
+ testCase.expectedErrorMessage);
+ } else {
+ ASSERT_VALUE_EQ(Value{testCase.expectedResult},
+ dateDiffExpression->evaluate({}, &(expCtx->variables)));
+ }
+ }
+}
+
+TEST_F(ExpressionDateDiffTest, OptimizesToConstantIfAllInputsAreConstant) {
+ auto dateDiffExpression = buildExpressionWithParameters(
+ Value{Date_t::fromMillisSinceEpoch(0)},
+ Value{Date_t::fromMillisSinceEpoch(31571873000) /*1971-mm-dd*/},
+ Value{"year"_sd},
+ Value{"GMT"_sd});
+
+ // Verify that 'optimize()' returns a constant expression when all parameters evaluate to
+ // constants.
+ auto optimizedDateDiffExpression1 = dateDiffExpression->optimize();
+ auto constantExpression = dynamic_cast<ExpressionConstant*>(optimizedDateDiffExpression1.get());
+ ASSERT(constantExpression);
+ ASSERT_VALUE_EQ(Value{1LL}, constantExpression->getValue());
+}
+
+TEST_F(ExpressionDateDiffTest, DoesNotOptimizeToConstantIfNotAllInputsAreConstant) {
+ auto dateDiffExpression = buildExpressionWithParameters(Value{Date_t::fromMillisSinceEpoch(0)},
+ Value{Date_t::fromMillisSinceEpoch(0)},
+ Value{"$year"_sd},
+ Value{} /* Time zone not specified*/);
+
+ // Verify that 'optimize()' returns a $dateDiff expression when not all parameters evaluate to
+ // constants.
+ auto optimizedDateDiffExpression = dateDiffExpression->optimize();
+ ASSERT(dynamic_cast<ExpressionDateDiff*>(optimizedDateDiffExpression.get()));
+ ASSERT_EQUALS(dateDiffExpression.get(), optimizedDateDiffExpression.get());
+}
+
+TEST_F(ExpressionDateDiffTest, AddsDependencies) {
+ auto dateDiffExpression = buildExpressionWithParameters(Value{"$startDateField"_sd},
+ Value{"$endDateField"_sd},
+ Value{"$unitField"_sd},
+ Value{"$timezoneField"_sd});
+
+ // Verify that dependencies for $dateDiff expression are determined correctly.
+ auto depsTracker = dateDiffExpression->getDependencies();
+ ASSERT_TRUE(
+ (depsTracker.fields ==
+ std::set<std::string>{"startDateField", "endDateField", "unitField", "timezoneField"}));
+}
+} // namespace ExpressionDateDiffTest
} // namespace mongo