diff options
Diffstat (limited to 'src/mongo/db/pipeline/expression_date_test.cpp')
-rw-r--r-- | src/mongo/db/pipeline/expression_date_test.cpp | 283 |
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 |