summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMindaugas Malinauskas <mindaugas.malinauskas@mongodb.com>2021-01-21 17:24:14 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-11 13:24:45 +0000
commite9b02873749f2331f1853d00e13c8a67b39bf53a (patch)
treef643cb0b752fa091a7f9853ec71a5dba9cfe5d7c
parent6d419110f2dbd9b07ee96a601356ddbe99628dc5 (diff)
downloadmongo-e9b02873749f2331f1853d00e13c8a67b39bf53a.tar.gz
SERVER-53388 Week start parameter for $dateDiff aggregation expression
-rw-r--r--jstests/aggregation/expressions/date_diff.js105
-rw-r--r--jstests/libs/sbe_assert_error_override.js3
-rw-r--r--src/mongo/db/exec/sbe/expression_test_base.h6
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.cpp4
-rw-r--r--src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp182
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.cpp33
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h2
-rw-r--r--src/mongo/db/pipeline/expression.cpp78
-rw-r--r--src/mongo/db/pipeline/expression.h21
-rw-r--r--src/mongo/db/pipeline/expression_bm.cpp10
-rw-r--r--src/mongo/db/pipeline/expression_date_test.cpp104
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp69
-rw-r--r--src/mongo/db/query/datetime/date_time_support.h37
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp72
-rw-r--r--src/mongo/db/query/sbe_stage_builder_expression.cpp146
-rw-r--r--src/mongo/db/query/sbe_stage_builder_helpers.cpp13
-rw-r--r--src/mongo/db/query/sbe_stage_builder_helpers.h7
17 files changed, 759 insertions, 133 deletions
diff --git a/jstests/aggregation/expressions/date_diff.js b/jstests/aggregation/expressions/date_diff.js
index 0d643c5caa2..bf4fc3554b3 100644
--- a/jstests/aggregation/expressions/date_diff.js
+++ b/jstests/aggregation/expressions/date_diff.js
@@ -46,6 +46,20 @@ const aggregationPipelineWithDateDiff = [{
}
}
}];
+const aggregationPipelineWithDateDiffAndStartOfWeek = [{
+ $project: {
+ _id: false,
+ date_diff: {
+ $dateDiff: {
+ startDate: "$startDate",
+ endDate: "$endDate",
+ unit: "$unit",
+ timezone: "$timeZone",
+ startOfWeek: "$startOfWeek"
+ }
+ }
+ }
+}];
const testCases = [
{
// Parameters are constants, timezone is not specified.
@@ -66,12 +80,13 @@ const testCases = [
},
{
// Parameters are field paths.
- pipeline: aggregationPipelineWithDateDiff,
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
inputDocuments: [{
startDate: new Date("2020-11-01T18:23:36Z"),
endDate: new Date("2020-11-02T00:00:00Z"),
unit: "hour",
- timeZone: "America/New_York"
+ timeZone: "America/New_York",
+ startOfWeek: "IGNORED" // Ignored when unit is not week.
}],
expectedResults: [{date_diff: NumberLong("6")}]
},
@@ -163,13 +178,13 @@ const testCases = [
expectedResults: [{date_diff: null}],
},
{
- // Wrong 'unit' type.
+ // Invalid 'unit' type.
pipeline: aggregationPipelineWithDateDiff,
inputDocuments: [{startDate: someDate, endDate: someDate, unit: 5, timeZone: "UTC"}],
expectedErrorCode: 5166306,
},
{
- // Wrong 'unit' value.
+ // Invalid 'unit' value.
pipeline: aggregationPipelineWithDateDiff,
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "decade", timeZone: "UTC"}],
expectedErrorCode: 9,
@@ -187,17 +202,95 @@ const testCases = [
expectedResults: [{date_diff: null}],
},
{
- // Wrong 'timezone' type.
+ // Invalid 'timezone' type.
pipeline: aggregationPipelineWithDateDiff,
inputDocuments: [{startDate: someDate, endDate: someDate, unit: "hour", timeZone: 1}],
expectedErrorCode: 40517,
},
{
- // Wrong 'timezone' value.
+ // Invalid 'timezone' value.
pipeline: aggregationPipelineWithDateDiff,
inputDocuments:
[{startDate: someDate, endDate: someDate, unit: "hour", timeZone: "America/Invalid"}],
expectedErrorCode: 40485,
+ },
+ {
+ // Specified 'startOfWeek'.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [{
+ startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
+ endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
+ unit: "week",
+ timeZone: "GMT",
+ startOfWeek: "MONDAY"
+ }],
+ expectedResults: [{date_diff: NumberLong("1")}],
+ },
+ {
+ // Specified 'startOfWeek' and timezone.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [{
+ startDate: new Date("2021-01-17T05:00:00Z"), // Sunday in New York.
+ endDate: new Date("2021-01-17T04:59:00Z"), // Saturday in New York.
+ unit: "week",
+ timeZone: "America/New_York",
+ startOfWeek: "sunday"
+ }],
+ expectedResults: [{date_diff: NumberLong("-1")}],
+ },
+ {
+ // Unspecified 'startOfWeek' - defaults to Sunday.
+ pipeline: [{
+ $project: {
+ _id: false,
+ date_diff: {$dateDiff: {startDate: "$startDate", endDate: "$endDate", unit: "week"}}
+ }
+ }],
+ inputDocuments: [{
+ startDate: new Date("2021-01-24T18:23:36Z"), // Sunday.
+ endDate: new Date("2021-01-25T02:23:36Z"), // Monday.
+ }],
+ expectedResults: [{date_diff: NumberLong("0")}],
+ },
+ {
+ // Null 'startOfWeek'.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [{startDate: someDate, endDate: someDate, unit: "week", startOfWeek: null}],
+ expectedResults: [{date_diff: null}],
+ },
+ {
+ // Missing 'startOfWeek' value, invalid other fields.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [{startDate: 1, endDate: 2, unit: "week", timeZone: 1}],
+ expectedResults: [{date_diff: null}],
+ },
+ {
+ // Invalid 'startOfWeek' type.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [
+ {startDate: someDate, endDate: someDate, unit: "week", timeZone: "GMT", startOfWeek: 1}
+ ],
+ expectedErrorCode: 5338800,
+ },
+ {
+ // Invalid 'startOfWeek' type, unit is not the week.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [
+ {startDate: someDate, endDate: someDate, unit: "hour", timeZone: "GMT", startOfWeek: 1}
+ ],
+ expectedResults: [{date_diff: NumberLong("0")}],
+ },
+ {
+ // Invalid 'startOfWeek' value.
+ pipeline: aggregationPipelineWithDateDiffAndStartOfWeek,
+ inputDocuments: [{
+ startDate: someDate,
+ endDate: someDate,
+ unit: "week",
+ timeZone: "GMT",
+ startOfWeek: "FRIDIE"
+ }],
+ expectedErrorCode: 9,
}
];
testCases.forEach(executeTestCase);
diff --git a/jstests/libs/sbe_assert_error_override.js b/jstests/libs/sbe_assert_error_override.js
index 256af6b042e..8dde30b3a00 100644
--- a/jstests/libs/sbe_assert_error_override.js
+++ b/jstests/libs/sbe_assert_error_override.js
@@ -21,7 +21,7 @@
// Below is the list of known equivalent error code groups. As new groups of equivalent error codes
// are discovered, they should be added to this list.
const equivalentErrorCodesList = [
- [9, 5166503, 5166605],
+ [9, 5166503, 5166605, 5338802],
[28651, 5073201],
[16006, 4997703, 4998202],
[28689, 5126701],
@@ -81,6 +81,7 @@ const equivalentErrorCodesList = [
[51111, 5073402],
[51151, 5126606],
[51156, 5073403],
+ [5338800, 5338801],
];
// This map is generated based on the contents of 'equivalentErrorCodesList'. This map should _not_
diff --git a/src/mongo/db/exec/sbe/expression_test_base.h b/src/mongo/db/exec/sbe/expression_test_base.h
index 5f5db70955f..a47476a802c 100644
--- a/src/mongo/db/exec/sbe/expression_test_base.h
+++ b/src/mongo/db/exec/sbe/expression_test_base.h
@@ -90,6 +90,12 @@ protected:
return _vm.runPredicate(compiledExpr);
}
+ void runAndAssertNothing(const vm::CodeFragment* compiledExpression) {
+ auto [resultTag, resultValue] = runCompiledExpression(compiledExpression);
+ value::ValueGuard guard(resultTag, resultValue);
+ ASSERT_EQUALS(resultTag, sbe::value::TypeTags::Nothing);
+ }
+
static std::pair<value::TypeTags, value::Value> makeBsonArray(const BSONArray& ba) {
return value::copyValue(value::TypeTags::bsonArray,
value::bitcastFrom<const char*>(ba.objdata()));
diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp
index b010f934470..ba23b8e9bf1 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.cpp
+++ b/src/mongo/db/exec/sbe/expressions/expression.cpp
@@ -350,7 +350,8 @@ struct BuiltinFn {
* The map of recognized builtin functions.
*/
static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
- {"dateDiff", BuiltinFn{[](size_t n) { return n == 5; }, vm::Builtin::dateDiff, false}},
+ {"dateDiff",
+ BuiltinFn{[](size_t n) { return n == 5 || n == 6; }, vm::Builtin::dateDiff, false}},
{"dateParts", BuiltinFn{[](size_t n) { return n == 9; }, vm::Builtin::dateParts, false}},
{"dateToParts",
BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::dateToParts, false}},
@@ -415,6 +416,7 @@ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::indexOfBytes, false}},
{"indexOfCP",
BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::indexOfCP, false}},
+ {"isDayOfWeek", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::isDayOfWeek, false}},
{"isTimeUnit", BuiltinFn{[](size_t n) { return n == 1; }, vm::Builtin::isTimeUnit, false}},
{"isTimezone", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::isTimezone, false}},
{"setUnion", BuiltinFn{[](size_t n) { return n >= 0; }, vm::Builtin::setUnion, false}},
diff --git a/src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp
index e73266de0b0..290fcbcfa98 100644
--- a/src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp
+++ b/src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp
@@ -35,19 +35,10 @@ namespace mongo::sbe {
namespace {
using SBEDateDiffTest = EExpressionTestFixture;
-
-/**
- * A fixture for SBE built-in function "isTimeUnit".
- */
-class SBEIsTimeUnitTest : public EExpressionTestFixture {
-public:
- void runAndAssertNothing(const vm::CodeFragment* compiledExpression) {
- auto [resultTag, resultValue] = runCompiledExpression(compiledExpression);
- value::ValueGuard guard(resultTag, resultValue);
- ASSERT_EQUALS(resultTag, sbe::value::TypeTags::Nothing);
- ASSERT_EQUALS(resultValue, 0);
- }
-};
+using SBEIsTimeUnitTest = EExpressionTestFixture;
+using SBEIsDayOfWeekTest = EExpressionTestFixture;
+const TimeZoneDatabase kDefaultTimeZoneDatabase{};
+const TimeZone kDefaultTimeZone = TimeZoneDatabase::utcZone();
/**
* Resets value accessor 'accessor' with string 'value'.
@@ -72,6 +63,24 @@ std::pair<value::TypeTags, value::Value> convertOIDToSbeValue(const OID& oid) {
std::pair<value::TypeTags, value::Value> convertTimestampToSbeValue(const Timestamp& timestamp) {
return {value::TypeTags::Timestamp, value::bitcastFrom<uint64_t>(timestamp.asULL())};
}
+
+/**
+ * Makes 64-bit integer SBE value and tag pair from 'value'.
+ */
+std::pair<value::TypeTags, value::Value> makeLongValue(long long value) {
+ return {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(value)};
+}
+
+/**
+ * Makes Date type SBE value and tag pair from date parts 'year', 'month' and so on.
+ */
+std::pair<value::TypeTags, value::Value> makeDateValue(
+ long long year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second) {
+ return {value::TypeTags::Date,
+ value::bitcastFrom<int64_t>(
+ kDefaultTimeZone.createFromDateParts(year, month, day, hour, minute, second, 0)
+ .toMillisSinceEpoch())};
+}
} // namespace
/**
@@ -88,7 +97,10 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
auto unitSlot = bindAccessor(&unitAccessor);
value::OwnedValueAccessor timezoneAccessor;
auto timezoneSlot = bindAccessor(&timezoneAccessor);
+ value::OwnedValueAccessor startOfWeekAccessor;
+ auto startOfWeekSlot = bindAccessor(&startOfWeekAccessor);
+ // Construct an invocation of "dateDiff" function without 'startOfWeek' parameter.
auto dateDiffExpression =
sbe::makeE<sbe::EFunction>("dateDiff",
sbe::makeEs(makeE<EVariable>(timezoneDBSlot),
@@ -98,6 +110,16 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
makeE<EVariable>(timezoneSlot)));
auto compiledDateDiff = compileExpression(*dateDiffExpression);
+ // Construct an invocation of "dateDiff" function with 'startOfWeek' parameter.
+ dateDiffExpression = sbe::makeE<sbe::EFunction>("dateDiff",
+ sbe::makeEs(makeE<EVariable>(timezoneDBSlot),
+ makeE<EVariable>(startDateSlot),
+ makeE<EVariable>(endDateSlot),
+ makeE<EVariable>(unitSlot),
+ makeE<EVariable>(timezoneSlot),
+ makeE<EVariable>(startOfWeekSlot)));
+ auto compiledDateDiffWithStartOfWeek = compileExpression(*dateDiffExpression);
+
// Setup timezone database.
auto timezoneDatabase = std::make_unique<TimeZoneDatabase>();
timezoneDBAccessor.reset(false,
@@ -109,58 +131,52 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
std::pair<value::TypeTags, value::Value> endDate;
std::pair<value::TypeTags, value::Value> unit;
std::pair<value::TypeTags, value::Value> timezone;
- std::pair<value::TypeTags, value::Value> expectedValue;
+ std::pair<value::TypeTags, value::Value> expectedValue; // Output.
+ boost::optional<std::pair<value::TypeTags, value::Value>> startOfWeek;
};
const std::pair<value::TypeTags, value::Value> kNothing{value::TypeTags::Nothing, 0};
- const std::pair<value::TypeTags, value::Value> kAnyDate{
- value::TypeTags::Date, value::bitcastFrom<int64_t>(1604255016000)}; // 2020-11-01 18:23:36
+ const std::pair<value::TypeTags, value::Value> kAnyDate{makeDateValue(2020, 11, 1, 18, 23, 36)};
const OID kOid = OID::gen();
std::vector<TestCase> testCases{
{// Sunny day case.
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604255016000)}, // 2020-11-01 18:23:36
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604260800000)}, // 2020-11-01 20:00:00
+ makeDateValue(2020, 11, 01, 18, 23, 36),
+ makeDateValue(2020, 11, 01, 20, 0, 0),
value::makeNewString("hour"),
value::makeNewString("GMT"),
- {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2LL)}},
+ makeLongValue(2)},
{// Accepts OID values.
convertOIDToSbeValue(kOid),
convertOIDToSbeValue(kOid),
value::makeNewString("millisecond"),
value::makeNewString("GMT"),
- {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(0LL)}},
+ makeLongValue(0)},
{// Accepts Timestamp values.
convertTimestampToSbeValue(Timestamp{Seconds{2}, 0}),
convertTimestampToSbeValue(Timestamp{Seconds{4}, 0}),
value::makeNewString("second"),
value::makeNewString("America/New_York"),
- {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2LL)}},
+ makeLongValue(2)},
{// 'startDate' is Nothing.
kNothing,
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604260800000)}, // 2020-11-01 20:00:00
+ makeDateValue(2020, 11, 01, 20, 0, 0),
value::makeNewString("hour"),
value::makeNewString("GMT"),
kNothing},
{// 'endDate' is Nothing.
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604260800000)}, // 2020-11-01 20:00:00
+ makeDateValue(2020, 11, 01, 20, 0, 0),
kNothing,
value::makeNewString("hour"),
value::makeNewString("GMT"),
kNothing},
{// 'startDate' is not a valid type.
{value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(0)},
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604260800000)}, // 2020-11-01 20:00:00
+ makeDateValue(2020, 11, 01, 20, 0, 0),
value::makeNewString("hour"),
value::makeNewString("GMT"),
kNothing},
{// 'endDate' is not a valid type.
- {value::TypeTags::Date,
- value::bitcastFrom<int64_t>(1604260800000)}, // 2020-11-01 20:00:00
+ makeDateValue(2020, 11, 01, 20, 0, 0),
{value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(0)},
value::makeNewString("hour"),
value::makeNewString("GMT"),
@@ -200,7 +216,69 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
kAnyDate,
value::makeNewString("hour"),
kNothing,
- kNothing}};
+ kNothing},
+ {
+ // 'startOfWeek' is present and invalid type.
+ kAnyDate,
+ kAnyDate,
+ value::makeNewString("hour"),
+ value::makeNewString("GMT"),
+ kNothing, // result
+ makeLongValue(1) // startOfWeek
+ },
+ {
+ // 'startOfWeek' is present, valid type but invalid value, unit is not week.
+ kAnyDate,
+ kAnyDate,
+ value::makeNewString("hour"),
+ value::makeNewString("GMT"),
+ makeLongValue(0), // result
+ value::makeNewString("INVALID") // startOfWeek
+ },
+ {
+ // 'startOfWeek' is Nothing, unit is week.
+ kAnyDate,
+ kAnyDate,
+ value::makeNewString("week"),
+ value::makeNewString("GMT"),
+ kNothing, // result
+ kNothing // startOfWeek
+ },
+ {
+ // 'startOfWeek' is invalid type, unit is week.
+ kAnyDate,
+ kAnyDate,
+ value::makeNewString("week"),
+ value::makeNewString("GMT"),
+ kNothing, // result
+ makeLongValue(0) // startOfWeek
+ },
+ {
+ // 'startOfWeek' is invalid value, unit is week.
+ kAnyDate,
+ kAnyDate,
+ value::makeNewString("week"),
+ value::makeNewString("GMT"),
+ kNothing, // result
+ value::makeNewString("holiday") // startOfWeek
+ },
+ {
+ // 'startOfWeek' is valid value, unit is week.
+ makeDateValue(2021, 01, 25, 8, 0, 0), // Monday
+ makeDateValue(2021, 01, 26, 8, 0, 0), // Tuesday
+ value::makeNewString("week"),
+ value::makeNewString("GMT"),
+ makeLongValue(1), // result
+ value::makeNewString("Tuesday") // startOfWeek
+ },
+ {
+ // 'startOfWeek' is not specified (should default to Sunday), unit is week.
+ makeDateValue(2021, 01, 23, 8, 0, 0), // Saturday
+ makeDateValue(2021, 01, 24, 8, 0, 0), // Sunday
+ value::makeNewString("week"),
+ value::makeNewString("GMT"),
+ makeLongValue(1) // result
+ }};
int testNumber{0};
for (auto&& testCase : testCases) {
@@ -208,9 +286,14 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
endDateAccessor.reset(testCase.endDate.first, testCase.endDate.second);
unitAccessor.reset(testCase.unit.first, testCase.unit.second);
timezoneAccessor.reset(testCase.timezone.first, testCase.timezone.second);
+ if (testCase.startOfWeek) {
+ startOfWeekAccessor.reset(testCase.startOfWeek->first, testCase.startOfWeek->second);
+ }
// Execute the "dateDiff" function.
- auto [resultTag, resultValue] = runCompiledExpression(compiledDateDiff.get());
+ auto result = runCompiledExpression(
+ (testCase.startOfWeek ? compiledDateDiffWithStartOfWeek : compiledDateDiff).get());
+ auto [resultTag, resultValue] = result;
value::ValueGuard resultGuard(resultTag, resultValue);
auto [compResultTag, compResultValue] = compareValue(
@@ -218,7 +301,8 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
value::ValueGuard compResultGuard(compResultTag, compResultValue);
ASSERT_EQUALS(compResultTag, value::TypeTags::NumberInt32);
- ASSERT_EQUALS(compResultValue, 0) << "Failed test #" << testNumber;
+ ASSERT_EQUALS(compResultValue, 0) << "Failed test #" << testNumber << ", result: " << result
+ << ", expected: " << testCase.expectedValue;
++testNumber;
}
}
@@ -226,7 +310,7 @@ TEST_F(SBEDateDiffTest, BasicDateDiff) {
/**
* A test for SBE built-in function "isTimeUnit".
*/
-TEST_F(SBEIsTimeUnitTest, BasicIsTimeUnit) {
+TEST_F(SBEIsTimeUnitTest, Basic) {
value::OwnedValueAccessor unitAccessor;
auto unitSlot = bindAccessor(&unitAccessor);
@@ -250,4 +334,32 @@ TEST_F(SBEIsTimeUnitTest, BasicIsTimeUnit) {
setValue("second", unitAccessor);
ASSERT_TRUE(runCompiledExpressionPredicate(compiledIsTimeUnitExpression.get()));
}
+
+/**
+ * A test for SBE built-in function "isDayOfWeek".
+ */
+TEST_F(SBEIsDayOfWeekTest, Basic) {
+ value::OwnedValueAccessor dayOfWeekAccessor;
+ auto dayOfWeekSlot = bindAccessor(&dayOfWeekAccessor);
+
+ auto isDayOfWeekExpression =
+ sbe::makeE<sbe::EFunction>("isDayOfWeek", sbe::makeEs(makeE<EVariable>(dayOfWeekSlot)));
+ auto compiledIsDayOfWeekExpression = compileExpression(*isDayOfWeekExpression);
+
+ // Verify that when passed Nothing returns Nothing.
+ dayOfWeekAccessor.reset(value::TypeTags::Nothing, 0);
+ runAndAssertNothing(compiledIsDayOfWeekExpression.get());
+
+ // Verify that when passed not a string returns Nothing.
+ dayOfWeekAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(5));
+ runAndAssertNothing(compiledIsDayOfWeekExpression.get());
+
+ // Verify that when passed an invalid unit returns false.
+ setValue("m1", dayOfWeekAccessor);
+ ASSERT_FALSE(runCompiledExpressionPredicate(compiledIsDayOfWeekExpression.get()));
+
+ // Verify that when passed a valid unit returns true.
+ setValue("Wednesday", dayOfWeekAccessor);
+ ASSERT_TRUE(runCompiledExpressionPredicate(compiledIsDayOfWeekExpression.get()));
+}
} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp
index 9a356dd70ad..1f8d7a616bc 100644
--- a/src/mongo/db/exec/sbe/vm/vm.cpp
+++ b/src/mongo/db/exec/sbe/vm/vm.cpp
@@ -1371,7 +1371,7 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDate(ArityType
}
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDateDiff(ArityType arity) {
- invariant(arity == 5);
+ invariant(arity == 5 || arity == 6); // 6th parameter is 'startOfWeek'.
auto [timezoneDBOwn, timezoneDBTag, timezoneDBValue] = getFromStack(0);
if (timezoneDBTag != value::TypeTags::timeZoneDB) {
@@ -1411,7 +1411,22 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDateDiff(ArityT
}
auto timezone = getTimezone(timezoneTag, timezoneValue, timezoneDB);
- auto result = dateDiff(startDate, endDate, unit, timezone);
+ // Get startOfWeek, if 'startOfWeek' parameter was passed and time unit is the week.
+ DayOfWeek startOfWeek{kStartOfWeekDefault};
+ if (6 == arity) {
+ auto [startOfWeekOwn, startOfWeekTag, startOfWeekValue] = getFromStack(5);
+ if (!value::isString(startOfWeekTag)) {
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ if (TimeUnit::week == unit) {
+ auto startOfWeekString = value::getStringView(startOfWeekTag, startOfWeekValue);
+ if (!isValidDayOfWeek(startOfWeekString)) {
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ startOfWeek = parseDayOfWeek(startOfWeekString);
+ }
+ }
+ auto result = dateDiff(startDate, endDate, unit, timezone, startOfWeek);
return {false, value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(result)};
}
@@ -2050,6 +2065,18 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsTimeUnit(Arit
isValidTimeUnit(value::getStringView(timeUnitTag, timeUnitValue)))};
}
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsDayOfWeek(ArityType arity) {
+ invariant(arity == 1);
+ auto [dayOfWeekOwn, dayOfWeekTag, dayOfWeekValue] = getFromStack(0);
+ if (!value::isString(dayOfWeekTag)) {
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ return {false,
+ value::TypeTags::Boolean,
+ value::bitcastFrom<bool>(
+ isValidDayOfWeek(value::getStringView(dayOfWeekTag, dayOfWeekValue)))};
+}
+
std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsTimezone(ArityType arity) {
auto [timezoneDBOwn, timezoneDBTag, timezoneDBVal] = getFromStack(0);
if (timezoneDBTag != value::TypeTags::timeZoneDB) {
@@ -2962,6 +2989,8 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti
return builtinIndexOfBytes(arity);
case Builtin::indexOfCP:
return builtinIndexOfCP(arity);
+ case Builtin::isDayOfWeek:
+ return builtinIsDayOfWeek(arity);
case Builtin::isTimeUnit:
return builtinIsTimeUnit(arity);
case Builtin::isTimezone:
diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h
index 67154cc887a..0a2d2f4cfda 100644
--- a/src/mongo/db/exec/sbe/vm/vm.h
+++ b/src/mongo/db/exec/sbe/vm/vm.h
@@ -282,6 +282,7 @@ enum class Builtin : uint8_t {
collIsMember,
indexOfBytes,
indexOfCP,
+ isDayOfWeek,
isTimeUnit,
isTimezone,
setUnion,
@@ -694,6 +695,7 @@ private:
std::tuple<bool, value::TypeTags, value::Value> builtinCollIsMember(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIndexOfBytes(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIndexOfCP(ArityType arity);
+ std::tuple<bool, value::TypeTags, value::Value> builtinIsDayOfWeek(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIsTimeUnit(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinIsTimezone(ArityType arity);
std::tuple<bool, value::TypeTags, value::Value> builtinSetUnion(ArityType arity);
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index ef26664f15b..545be1a8a9d 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -1860,13 +1860,19 @@ ExpressionDateDiff::ExpressionDateDiff(ExpressionContext* const expCtx,
boost::intrusive_ptr<Expression> startDate,
boost::intrusive_ptr<Expression> endDate,
boost::intrusive_ptr<Expression> unit,
- boost::intrusive_ptr<Expression> timezone)
+ boost::intrusive_ptr<Expression> timezone,
+ boost::intrusive_ptr<Expression> startOfWeek)
: Expression{expCtx,
- {std::move(startDate), std::move(endDate), std::move(unit), std::move(timezone)}},
+ {std::move(startDate),
+ std::move(endDate),
+ std::move(unit),
+ std::move(timezone),
+ std::move(startOfWeek)}},
_startDate{_children[0]},
_endDate{_children[1]},
_unit{_children[2]},
- _timeZone{_children[3]} {}
+ _timeZone{_children[3]},
+ _startOfWeek{_children[4]} {}
boost::intrusive_ptr<Expression> ExpressionDateDiff::parse(ExpressionContext* const expCtx,
BSONElement expr,
@@ -1875,7 +1881,7 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::parse(ExpressionContext* co
uassert(5166301,
"$dateDiff only supports an object as its argument",
expr.type() == BSONType::Object);
- BSONElement startDateElement, endDateElement, unitElement, timezoneElem;
+ BSONElement startDateElement, endDateElement, unitElement, timezoneElem, startOfWeekElem;
for (auto&& element : expr.embeddedObject()) {
auto field = element.fieldNameStringData();
if ("startDate"_sd == field) {
@@ -1886,6 +1892,8 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::parse(ExpressionContext* co
unitElement = element;
} else if ("timezone"_sd == field) {
timezoneElem = element;
+ } else if ("startOfWeek"_sd == field) {
+ startOfWeekElem = element;
} else {
uasserted(5166302,
str::stream()
@@ -1895,12 +1903,13 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::parse(ExpressionContext* co
uassert(5166303, "Missing 'startDate' parameter to $dateDiff", startDateElement);
uassert(5166304, "Missing 'endDate' parameter to $dateDiff", endDateElement);
uassert(5166305, "Missing 'unit' parameter to $dateDiff", unitElement);
- return make_intrusive<ExpressionDateDiff>(expCtx,
- parseOperand(expCtx, startDateElement, vps),
- parseOperand(expCtx, endDateElement, vps),
- parseOperand(expCtx, unitElement, vps),
- timezoneElem ? parseOperand(expCtx, timezoneElem, vps)
- : nullptr);
+ return make_intrusive<ExpressionDateDiff>(
+ expCtx,
+ parseOperand(expCtx, startDateElement, vps),
+ parseOperand(expCtx, endDateElement, vps),
+ parseOperand(expCtx, unitElement, vps),
+ timezoneElem ? parseOperand(expCtx, timezoneElem, vps) : nullptr,
+ startOfWeekElem ? parseOperand(expCtx, startOfWeekElem, vps) : nullptr);
}
boost::intrusive_ptr<Expression> ExpressionDateDiff::optimize() {
@@ -1910,7 +1919,11 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::optimize() {
if (_timeZone) {
_timeZone = _timeZone->optimize();
}
- if (ExpressionConstant::allNullOrConstant({_startDate, _endDate, _unit, _timeZone})) {
+ if (_startOfWeek) {
+ _startOfWeek = _startOfWeek->optimize();
+ }
+ if (ExpressionConstant::allNullOrConstant(
+ {_startDate, _endDate, _unit, _timeZone, _startOfWeek})) {
// Everything is a constant, so we can turn into a constant.
return ExpressionConstant::create(
getExpressionContext(), evaluate(Document{}, &(getExpressionContext()->variables)));
@@ -1919,12 +1932,13 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::optimize() {
};
Value ExpressionDateDiff::serialize(bool explain) const {
- return Value{
- Document{{"$dateDiff"_sd,
- Document{{"startDate"_sd, _startDate->serialize(explain)},
- {"endDate"_sd, _endDate->serialize(explain)},
- {"unit"_sd, _unit->serialize(explain)},
- {"timezone"_sd, _timeZone ? _timeZone->serialize(explain) : Value{}}}}}};
+ return Value{Document{
+ {"$dateDiff"_sd,
+ Document{{"startDate"_sd, _startDate->serialize(explain)},
+ {"endDate"_sd, _endDate->serialize(explain)},
+ {"unit"_sd, _unit->serialize(explain)},
+ {"timezone"_sd, _timeZone ? _timeZone->serialize(explain) : Value{}},
+ {"startOfWeek"_sd, _startOfWeek ? _startOfWeek->serialize(explain) : Value{}}}}}};
};
Date_t ExpressionDateDiff::convertToDate(const Value& value, StringData parameterName) {
@@ -1962,6 +1976,19 @@ TimeUnit ExpressionDateDiff::convertToTimeUnit(const Value& value) {
"$dateDiff parameter 'unit' value parsing failed"_sd);
}
+DayOfWeek ExpressionDateDiff::parseStartOfWeek(const Value& value) {
+ uassert(5338800,
+ str::stream() << "$dateDiff requires 'startOfWeek' to be a string, but got "
+ << typeName(value.getType()),
+ BSONType::String == value.getType());
+ auto valueAsString = value.getStringData();
+ return addContextToAssertionException(
+ [&]() {
+ return parseDayOfWeek(std::string_view{valueAsString.rawData(), valueAsString.size()});
+ },
+ "$dateDiff parameter 'startOfWeek' value parsing failed"_sd);
+}
+
Value ExpressionDateDiff::evaluate(const Document& root, Variables* variables) const {
const Value startDateValue = _startDate->evaluate(root, variables);
if (startDateValue.nullish()) {
@@ -1975,6 +2002,16 @@ Value ExpressionDateDiff::evaluate(const Document& root, Variables* variables) c
if (unitValue.nullish()) {
return Value(BSONNULL);
}
+ const auto startOfWeekParameterActive = _startOfWeek &&
+ BSONType::String == unitValue.getType() && unitValue.getStringData() == "week"_sd;
+ Value startOfWeekValue{};
+ if (startOfWeekParameterActive) {
+ startOfWeekValue = _startOfWeek->evaluate(root, variables);
+ if (startOfWeekValue.nullish()) {
+ return Value(BSONNULL);
+ }
+ }
+
const auto timezone = addContextToAssertionException(
[&]() {
return makeTimeZone(
@@ -1987,7 +2024,9 @@ Value ExpressionDateDiff::evaluate(const Document& root, Variables* variables) c
const Date_t startDate = convertToDate(startDateValue, "startDate"_sd);
const Date_t endDate = convertToDate(endDateValue, "endDate"_sd);
const TimeUnit unit = convertToTimeUnit(unitValue);
- return Value{dateDiff(startDate, endDate, unit, *timezone)};
+ const DayOfWeek startOfWeek =
+ startOfWeekParameterActive ? parseStartOfWeek(startOfWeekValue) : kStartOfWeekDefault;
+ return Value{dateDiff(startDate, endDate, unit, *timezone, startOfWeek)};
}
void ExpressionDateDiff::_doAddDependencies(DepsTracker* deps) const {
@@ -1997,6 +2036,9 @@ void ExpressionDateDiff::_doAddDependencies(DepsTracker* deps) const {
if (_timeZone) {
_timeZone->addDependencies(deps);
}
+ if (_startOfWeek) {
+ _startOfWeek->addDependencies(deps);
+ }
}
/* ----------------------- ExpressionDivide ---------------------------- */
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 638f000969d..b2c657b8068 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -1372,12 +1372,15 @@ public:
* resolves to a string Value.
* timezone - expression defining a timezone to perform the operation in that resolves to a
* string Value. Can be nullptr.
+ * startOfWeek - expression defining the week start day that resolves to a string Value. Can be
+ * nullptr.
*/
ExpressionDateDiff(ExpressionContext* const expCtx,
boost::intrusive_ptr<Expression> startDate,
boost::intrusive_ptr<Expression> endDate,
boost::intrusive_ptr<Expression> unit,
- boost::intrusive_ptr<Expression> timezone);
+ boost::intrusive_ptr<Expression> timezone,
+ boost::intrusive_ptr<Expression> startOfWeek);
boost::intrusive_ptr<Expression> optimize() final;
Value serialize(bool explain) const final;
Value evaluate(const Document& root, Variables* variables) const final;
@@ -1395,6 +1398,13 @@ public:
return static_cast<bool>(_timeZone);
}
+ /**
+ * Returns true if this expression has parameter 'startOfWeek' specified, otherwise false.
+ */
+ bool isStartOfWeekSpecified() const {
+ return static_cast<bool>(_startOfWeek);
+ }
+
protected:
void _doAddDependencies(DepsTracker* deps) const final;
@@ -1409,6 +1419,11 @@ private:
*/
static TimeUnit convertToTimeUnit(const Value& value);
+ /**
+ * Converts 'value' to DayOfWeek for $dateDiff expression parameter 'startOfWeek'.
+ */
+ static DayOfWeek parseStartOfWeek(const Value& value);
+
// Starting time instant expression. Accepted types: Date_t, Timestamp, OID.
boost::intrusive_ptr<Expression>& _startDate;
@@ -1422,6 +1437,10 @@ private:
// Timezone to use for the difference calculation. Accepted type: std::string. If not specified,
// UTC is used.
boost::intrusive_ptr<Expression>& _timeZone;
+
+ // First/start day of the week to use for the date difference calculation when time unit is the
+ // week. Accepted type: std::string. If not specified, "sunday" is used.
+ boost::intrusive_ptr<Expression>& _startOfWeek;
};
class ExpressionDivide final : public ExpressionFixedArity<ExpressionDivide, 2> {
diff --git a/src/mongo/db/pipeline/expression_bm.cpp b/src/mongo/db/pipeline/expression_bm.cpp
index 17db38e32ef..a701c0665a8 100644
--- a/src/mongo/db/pipeline/expression_bm.cpp
+++ b/src/mongo/db/pipeline/expression_bm.cpp
@@ -45,12 +45,15 @@ namespace {
* endDate - end date in milliseconds from the UNIX epoch.
* unit - a string expression of units to use for date difference calculation.
* timezone - a string representation of timezone to use for date difference calculation.
+ * startOfWeek - a string representation of the first day of the week to use for date difference
+ * calculation when unit is a week.
* state - benchmarking state.
*/
void testDateDiffExpression(long long startDate,
long long endDate,
std::string unit,
boost::optional<std::string> timezone,
+ boost::optional<std::string> startOfWeek,
benchmark::State& state) {
QueryTestServiceContext testServiceContext;
auto opContext = testServiceContext.makeOperationContext();
@@ -65,6 +68,9 @@ void testDateDiffExpression(long long startDate,
if (timezone) {
objBuilder << "timezone" << *timezone;
}
+ if (startOfWeek) {
+ objBuilder << "startOfWeek" << *startOfWeek;
+ }
auto expression = BSON("$dateDiff" << objBuilder.obj());
auto dateDiffExpression = Expression::parseExpression(
exprContext.get(), expression, exprContext->variablesParseState);
@@ -85,6 +91,7 @@ void BM_DateDiffEvaluateMinute300Years(benchmark::State& state) {
7826117722000LL /* 2218-01-01*/,
"minute",
boost::none /*timezone*/,
+ boost::none /*startOfWeek*/,
state);
}
@@ -93,6 +100,7 @@ void BM_DateDiffEvaluateMinute2Years(benchmark::State& state) {
1605607121000LL /* 2020-11-17*/,
"minute",
boost::none /*timezone*/,
+ boost::none /*startOfWeek*/,
state);
}
@@ -101,6 +109,7 @@ void BM_DateDiffEvaluateMinute2YearsWithTimezone(benchmark::State& state) {
1605607121000LL /* 2020-11-17*/,
"minute",
std::string{"America/New_York"},
+ boost::none /*startOfWeek*/,
state);
}
@@ -109,6 +118,7 @@ void BM_DateDiffEvaluateWeek(benchmark::State& state) {
4761280721000LL /*2120-11-17*/,
"week",
boost::none /*timezone*/,
+ std::string("Sunday") /*startOfWeek*/,
state);
}
diff --git a/src/mongo/db/pipeline/expression_date_test.cpp b/src/mongo/db/pipeline/expression_date_test.cpp
index 39ec1628acd..160af9ec383 100644
--- a/src/mongo/db/pipeline/expression_date_test.cpp
+++ b/src/mongo/db/pipeline/expression_date_test.cpp
@@ -1443,11 +1443,13 @@ public:
/**
* Builds a $dateDiff expression with given values of parameters.
*/
- auto buildExpressionWithParameters(Value startDate, Value endDate, Value unit, Value timezone) {
+ auto buildExpressionWithParameters(
+ Value startDate, Value endDate, Value unit, Value timezone, Value startOfWeek = Value{}) {
auto expCtx = getExpCtx();
auto expression =
BSON("$dateDiff" << BSON("startDate" << startDate << "endDate" << endDate << "unit"
- << unit << "timezone" << timezone));
+ << unit << "timezone" << timezone << "startOfWeek"
+ << startOfWeek));
return Expression::parseExpression(expCtx.get(), expression, expCtx->variablesParseState);
}
};
@@ -1460,7 +1462,9 @@ TEST_F(ExpressionDateDiffTest, ParsesAndSerializesValidExpression) {
<< "unit"
<< "day"
<< "timezone"
- << "America/New_York")),
+ << "America/New_York"
+ << "startOfWeek"
+ << "Monday")),
BSON("$dateDiff" << BSON("startDate"
<< "$startDateField"
<< "endDate"
@@ -1470,7 +1474,10 @@ TEST_F(ExpressionDateDiffTest, ParsesAndSerializesValidExpression) {
<< "day")
<< "timezone"
<< BSON("$const"
- << "America/New_York"))));
+ << "America/New_York")
+ << "startOfWeek"
+ << BSON("$const"
+ << "Monday"))));
assertParsesAndSerializesExpression(BSON("$dateDiff" << BSON("startDate"
<< "$startDateField"
<< "endDate"
@@ -1538,6 +1545,7 @@ TEST_F(ExpressionDateDiffTest, EvaluatesExpression) {
Value expectedResult;
int expectedErrorCode{0};
std::string expectedErrorMessage;
+ Value startOfWeek;
};
auto expCtx = getExpCtx();
const auto anyDate = Value{Date_t{}};
@@ -1623,11 +1631,84 @@ TEST_F(ExpressionDateDiffTest, EvaluatesExpression) {
Value{Timestamp{Seconds(1604260800), 0} /* 2020-11-01T20:00:00 UTC+00:00 */},
Value{"minute"_sd},
Value{} /* 'timezone' not specified*/,
- Value{97}}};
+ Value{97}},
+ {
+ // Ignores 'startOfWeek' parameter value when unit is not week.
+ anyDate,
+ anyDate,
+ Value{"day"_sd},
+ Value{}, //'timezone' is not specified
+ Value{0}, // expectedResult
+ 0, // expectedErrorCode
+ "", // expectedErrorMessage
+ Value{"INVALID"_sd} // startOfWeek
+ },
+ {
+ // 'startOfWeek' is null.
+ anyDate,
+ anyDate,
+ Value{"week"_sd}, // unit
+ Value{}, //'timezone' is not specified
+ null, // expectedResult
+ 0, // expectedErrorCode
+ "", // expectedErrorMessage
+ null // startOfWeek
+ },
+ {
+ // Invalid 'startOfWeek' value type.
+ anyDate,
+ anyDate,
+ Value{"week"_sd}, // unit
+ Value{}, //'timezone' is not specified
+ null, // expectedResult
+ 5338800, // expectedErrorCode
+ "$dateDiff requires 'startOfWeek' to be a string, but got int", // expectedErrorMessage
+ Value{1} // startOfWeek
+ },
+ {
+ // Invalid 'startOfWeek' value.
+ anyDate,
+ anyDate,
+ Value{"week"_sd}, // unit
+ Value{}, //'timezone' is not specified
+ null, // expectedResult
+ ErrorCodes::FailedToParse, // expectedErrorCode
+ "$dateDiff parameter 'startOfWeek' value parsing failed :: caused by :: unknown day of "
+ "week value: Satur", // expectedErrorMessage
+ Value{"Satur"_sd} // startOfWeek
+ },
+ {
+ // Sunny day case for 'startOfWeek'.
+ Value{Date_t::fromMillisSinceEpoch(
+ 1611446400000) /* 2021-01-24T00:00:00 UTC+00:00 Sunday*/},
+ Value{Date_t::fromMillisSinceEpoch(
+ 1611532800000) /* 2021-01-25T00:00:00 UTC+00:00 Monday*/},
+ Value{"week"_sd}, // unit
+ Value{}, //'timezone' is not specified
+ Value{1}, // expectedResult
+ 0, // expectedErrorCode
+ "", // expectedErrorMessage
+ Value{"Monday"_sd} // startOfWeek
+ },
+ {
+ // 'startOfWeek' not specified, defaults to "Sunday".
+ Value{Date_t::fromMillisSinceEpoch(
+ 1611360000000) /* 2021-01-23T00:00:00 UTC+00:00 Saturday*/},
+ Value{Date_t::fromMillisSinceEpoch(
+ 1611446400000) /* 2021-01-24T00:00:00 UTC+00:00 Sunday*/},
+ Value{"week"_sd}, // unit
+ Value{}, //'timezone' is not specified
+ Value{1}, // expectedResult
+ },
+ };
+ // Week time unit and 'startOfWeek' specific test cases.
for (auto&& testCase : testCases) {
- auto dateDiffExpression = buildExpressionWithParameters(
- testCase.startDate, testCase.endDate, testCase.unit, testCase.timezone);
+ auto dateDiffExpression = buildExpressionWithParameters(testCase.startDate,
+ testCase.endDate,
+ testCase.unit,
+ testCase.timezone,
+ testCase.startOfWeek);
if (testCase.expectedErrorCode) {
ASSERT_THROWS_CODE_AND_WHAT(dateDiffExpression->evaluate({}, &(expCtx->variables)),
AssertionException,
@@ -1645,7 +1726,8 @@ TEST_F(ExpressionDateDiffTest, OptimizesToConstantIfAllInputsAreConstant) {
Value{Date_t::fromMillisSinceEpoch(0)},
Value{Date_t::fromMillisSinceEpoch(31571873000) /*1971-mm-dd*/},
Value{"year"_sd},
- Value{"GMT"_sd});
+ Value{"GMT"_sd},
+ Value{"Sunday"_sd});
// Verify that 'optimize()' returns a constant expression when all parameters evaluate to
// constants.
@@ -1672,13 +1754,15 @@ TEST_F(ExpressionDateDiffTest, AddsDependencies) {
auto dateDiffExpression = buildExpressionWithParameters(Value{"$startDateField"_sd},
Value{"$endDateField"_sd},
Value{"$unitField"_sd},
- Value{"$timezoneField"_sd});
+ Value{"$timezoneField"_sd},
+ Value{"$startOfWeekField"_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"}));
+ std::set<std::string>{
+ "startDateField", "endDateField", "unitField", "timezoneField", "startOfWeekField"}));
}
} // namespace ExpressionDateDiffTest
diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp
index ea2f603b8bf..096504a9e46 100644
--- a/src/mongo/db/query/datetime/date_time_support.cpp
+++ b/src/mongo/db/query/datetime/date_time_support.cpp
@@ -590,7 +590,7 @@ auto const kDaysInNonLeapYear = 365LL;
auto const kHoursPerDay = 24LL;
auto const kMinutesPerHour = 60LL;
auto const kSecondsPerMinute = 60LL;
-auto const kDaysPerWeek = 7LL;
+auto const kDaysPerWeek = 7;
auto const kQuartersPerYear = 4LL;
auto const kQuarterLengthInMonths = 3LL;
auto const kLeapYearReferencePoint = -1000000000L;
@@ -651,11 +651,27 @@ inline long long dateDiffDay(timelib_time* startInstant, timelib_time* endInstan
timelib_day_of_year(startInstant->y, startInstant->m, startInstant->d) +
daysBetweenYears(startInstant->y, endInstant->y);
}
-inline long long dateDiffWeek(timelib_time* startInstant, timelib_time* endInstant) {
- // We use 'timelib_iso_day_of_week()' since it considers Monday as the first day of the week.
- return (dateDiffDay(startInstant, endInstant) +
- timelib_iso_day_of_week(startInstant->y, startInstant->m, startInstant->d) -
- timelib_iso_day_of_week(endInstant->y, endInstant->m, endInstant->d)) /
+
+/**
+ * Determines which day of the week time instant 'timeInstant' is in given that the week starts on
+ * day 'startOfWeek'. Returns 0 for the first day, and 6 - for the last.
+ */
+inline unsigned int dayOfWeek(timelib_time* timeInstant, DayOfWeek startOfWeek) {
+ // We use 'timelib_iso_day_of_week()' since it returns value 1 for Monday.
+ return (timelib_iso_day_of_week(timeInstant->y, timeInstant->m, timeInstant->d) -
+ static_cast<uint8_t>(startOfWeek) + kDaysPerWeek) %
+ kDaysPerWeek;
+}
+
+/**
+ * Determines a number of weeks between time instant 'startInstant' and 'endInstant' when the first
+ * day of the week is 'startOfWeek'.
+ */
+inline long long dateDiffWeek(timelib_time* startInstant,
+ timelib_time* endInstant,
+ DayOfWeek startOfWeek) {
+ return (dateDiffDay(startInstant, endInstant) + dayOfWeek(startInstant, startOfWeek) -
+ dayOfWeek(endInstant, startOfWeek)) /
kDaysPerWeek;
}
inline long long dateDiffHour(timelib_time* startInstant, timelib_time* endInstant) {
@@ -690,9 +706,31 @@ static const StringMap<TimeUnit> timeUnitNameToTimeUnitMap{
{"second", TimeUnit::second},
{"millisecond", TimeUnit::millisecond},
};
+
+// A mapping from string representations of a day of a week to DayOfWeek.
+static const StringMap<DayOfWeek> dayOfWeekNameToDayOfWeekMap{
+ {"monday", DayOfWeek::monday},
+ {"mon", DayOfWeek::monday},
+ {"tuesday", DayOfWeek::tuesday},
+ {"tue", DayOfWeek::tuesday},
+ {"wednesday", DayOfWeek::wednesday},
+ {"wed", DayOfWeek::wednesday},
+ {"thursday", DayOfWeek::thursday},
+ {"thu", DayOfWeek::thursday},
+ {"friday", DayOfWeek::friday},
+ {"fri", DayOfWeek::friday},
+ {"saturday", DayOfWeek::saturday},
+ {"sat", DayOfWeek::saturday},
+ {"sunday", DayOfWeek::sunday},
+ {"sun", DayOfWeek::sunday},
+};
} // namespace
-long long dateDiff(Date_t startDate, Date_t endDate, TimeUnit unit, const TimeZone& timezone) {
+long long dateDiff(Date_t startDate,
+ Date_t endDate,
+ TimeUnit unit,
+ const TimeZone& timezone,
+ DayOfWeek startOfWeek) {
if (TimeUnit::millisecond == unit) {
return dateDiffMillisecond(startDate, endDate);
}
@@ -708,7 +746,7 @@ long long dateDiff(Date_t startDate, Date_t endDate, TimeUnit unit, const TimeZo
case TimeUnit::month:
return dateDiffMonth(startDateInTimeZone.get(), endDateInTimeZone.get());
case TimeUnit::week:
- return dateDiffWeek(startDateInTimeZone.get(), endDateInTimeZone.get());
+ return dateDiffWeek(startDateInTimeZone.get(), endDateInTimeZone.get(), startOfWeek);
case TimeUnit::day:
return dateDiffDay(startDateInTimeZone.get(), endDateInTimeZone.get());
case TimeUnit::hour:
@@ -758,6 +796,21 @@ StringData serializeTimeUnit(TimeUnit unit) {
MONGO_UNREACHABLE_TASSERT(5339900);
}
+DayOfWeek parseDayOfWeek(StringData dayOfWeek) {
+ // Perform case-insensitive lookup.
+ auto iterator = dayOfWeekNameToDayOfWeekMap.find(str::toLower(dayOfWeek));
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "unknown day of week value: " << dayOfWeek,
+ iterator != dayOfWeekNameToDayOfWeekMap.end());
+ return iterator->second;
+}
+
+bool isValidDayOfWeek(StringData dayOfWeek) {
+ // Perform case-insensitive lookup.
+ return dayOfWeekNameToDayOfWeekMap.find(str::toLower(dayOfWeek)) !=
+ dayOfWeekNameToDayOfWeekMap.end();
+}
+
void TimelibRelTimeDeleter::operator()(timelib_rel_time* relTime) {
timelib_rel_time_dtor(relTime);
}
diff --git a/src/mongo/db/query/datetime/date_time_support.h b/src/mongo/db/query/datetime/date_time_support.h
index 43bc84d54fb..b4c1fca1007 100644
--- a/src/mongo/db/query/datetime/date_time_support.h
+++ b/src/mongo/db/query/datetime/date_time_support.h
@@ -65,6 +65,21 @@ enum class TimeUnit {
};
/**
+ * Day of a week.
+ */
+enum class DayOfWeek : uint8_t {
+ monday = 1,
+ tuesday,
+ wednesday,
+ thursday,
+ friday,
+ saturday,
+ sunday
+};
+
+static constexpr DayOfWeek kStartOfWeekDefault{DayOfWeek::sunday};
+
+/**
* A TimeZone object represents one way of formatting/reading dates to compute things like the day
* of the week or the hour of a given date. Different TimeZone objects may report different answers
* for the hour, minute, or second of a date, even when given the same date.
@@ -511,6 +526,20 @@ bool isValidTimeUnit(StringData unitName);
StringData serializeTimeUnit(TimeUnit unit);
/**
+ * Parses a string 'dayOfWeek' to a DayOfWeek value. Supported day of week representations are
+ * case-insensitive full words or three letter abbreviations - for example, sunday, Sun. Throws an
+ * exception with error code ErrorCodes::FailedToParse when passed an invalid value.
+ */
+DayOfWeek parseDayOfWeek(StringData dayOfWeek);
+
+/**
+ * Returns true if 'dayOfWeek' is a valid representation of a day of a week, meaning that it can be
+ * parsed by the 'parseDayOfWeek()' function into one of the days represented by the 'DayOfWeek'
+ * enum. Otherwise returns 'false'.
+ */
+bool isValidDayOfWeek(StringData dayOfWeek);
+
+/**
* A custom-deleter which destructs a timelib_rel_time* when it goes out of scope.
*/
struct TimelibRelTimeDeleter {
@@ -550,8 +579,14 @@ std::unique_ptr<_timelib_rel_time, TimelibRelTimeDeleter> getTimelibRelTime(Time
* unit - length of time intervals.
* timezone - determines the timezone used for counting the boundaries as well as Daylight Saving
* Time rules.
+ * startOfWeek - the first day of a week used, to determine week boundaries when 'unit' is
+ * TimeUnit::week. Otherwise, this parameter is ignored.
*/
-long long dateDiff(Date_t startDate, Date_t endDate, TimeUnit unit, const TimeZone& timezone);
+long long dateDiff(Date_t startDate,
+ Date_t endDate,
+ TimeUnit unit,
+ const TimeZone& timezone,
+ DayOfWeek startOfWeek = kStartOfWeekDefault);
/**
* Add time interval to a date. The interval duration is given in 'amount' number of 'units'.
diff --git a/src/mongo/db/query/datetime/date_time_support_test.cpp b/src/mongo/db/query/datetime/date_time_support_test.cpp
index 2204ad5f306..08fc307b818 100644
--- a/src/mongo/db/query/datetime/date_time_support_test.cpp
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -1267,46 +1267,87 @@ TEST(DateDiff, Quarter) {
// Verifies 'dateDiff()' with TimeUnit::week.
TEST(DateDiff, Week) {
+ // Cases when the first day of the week is Monday.
ASSERT_EQ(1,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(1,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(0,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(0,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(0,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 3, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(1,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(1,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
ASSERT_EQ(-5,
dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 10, 0, 0, 0, 0),
kNewYorkTimeZone.createFromDateParts(2020, 10, 8, 0, 0, 0, 0),
TimeUnit::week,
- kNewYorkTimeZone));
+ kNewYorkTimeZone,
+ DayOfWeek::monday));
+
+ // Cases when the first day of the week is not Monday.
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 13, 0, 0, 0, 0), // Friday.
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0), // Sunday.
+ TimeUnit::week,
+ kNewYorkTimeZone,
+ DayOfWeek::sunday));
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 13, 0, 0, 0, 0), // Friday.
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 14, 0, 0, 0, 0), // Saturday.
+ TimeUnit::week,
+ kNewYorkTimeZone,
+ DayOfWeek::sunday));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0), // Sunday.
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0), // Sunday.
+ TimeUnit::week,
+ kNewYorkTimeZone,
+ DayOfWeek::sunday));
+ ASSERT_EQ(2,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 7, 0, 0, 0, 0), // Saturday.
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0), // Sunday.
+ TimeUnit::week,
+ kNewYorkTimeZone,
+ DayOfWeek::sunday));
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 4, 0, 0, 0, 0), // Wednesday.
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 10, 0, 0, 0, 0), // Tuesday.
+ TimeUnit::week,
+ kNewYorkTimeZone,
+ DayOfWeek::wednesday));
}
// Verifies 'dateDiff()' with TimeUnit::day.
@@ -1819,5 +1860,20 @@ TEST(DateAdd, DateAddMillisecond) {
ASSERT_EQ(dateAdd(startDate, TimeUnit::millisecond, -1500, kDefaultTimeZone),
kDefaultTimeZone.createFromDateParts(2020, 12, 31, 23, 59, 13, 500));
}
+
+TEST(IsValidDayOfWeek, Basic) {
+ ASSERT(isValidDayOfWeek("monday"));
+ ASSERT(isValidDayOfWeek("tue"));
+ ASSERT(isValidDayOfWeek("Wednesday"));
+ ASSERT(isValidDayOfWeek("FRIDAY"));
+ ASSERT(!isValidDayOfWeek(""));
+ ASSERT(!isValidDayOfWeek("SND"));
+}
+
+TEST(ParseDayOfWeek, Basic) {
+ ASSERT(DayOfWeek::thursday == parseDayOfWeek("thursday"));
+ ASSERT(DayOfWeek::saturday == parseDayOfWeek("SAT"));
+ ASSERT_THROWS_CODE(parseDayOfWeek(""), AssertionException, ErrorCodes::FailedToParse);
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/query/sbe_stage_builder_expression.cpp b/src/mongo/db/query/sbe_stage_builder_expression.cpp
index 51770173207..d05ceb5bf34 100644
--- a/src/mongo/db/query/sbe_stage_builder_expression.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_expression.cpp
@@ -1195,6 +1195,7 @@ public:
visitConditionalExpression(expr);
}
void visit(ExpressionDateDiff* expr) final {
+ using namespace std::literals;
auto frameId = _context->frameIdGenerator->generate();
std::vector<std::unique_ptr<sbe::EExpression>> arguments;
std::vector<std::unique_ptr<sbe::EExpression>> bindings;
@@ -1202,14 +1203,21 @@ public:
sbe::EVariable endDateRef(frameId, 1);
sbe::EVariable unitRef(frameId, 2);
sbe::EVariable timezoneRef(frameId, 3);
+ sbe::EVariable startOfWeekRef(frameId, 4);
+
+ // An auxiliary boolean variable to hold a value of a common subexpression 'unit'=="week"
+ // (string).
+ sbe::EVariable unitIsWeekRef(frameId, 5);
auto children = expr->getChildren();
- invariant(children.size() == 4);
- _context->ensureArity(expr->isTimezoneSpecified() ? 4 : 3);
+ invariant(children.size() == 5);
+ _context->ensureArity(3 + (expr->isTimezoneSpecified() ? 1 : 0) +
+ (expr->isStartOfWeekSpecified() ? 1 : 0));
// Get child expressions.
+ auto startOfWeekExpression = expr->isStartOfWeekSpecified() ? _context->popExpr() : nullptr;
auto timezoneExpression =
- expr->isTimezoneSpecified() ? _context->popExpr() : sbe::makeE<sbe::EConstant>("UTC");
+ expr->isTimezoneSpecified() ? _context->popExpr() : makeConstant("UTC"sv);
auto unitExpression = _context->popExpr();
auto endDateExpression = _context->popExpr();
auto startDateExpression = _context->popExpr();
@@ -1222,53 +1230,95 @@ public:
arguments.push_back(endDateRef.clone());
arguments.push_back(unitRef.clone());
arguments.push_back(timezoneRef.clone());
+ if (expr->isStartOfWeekSpecified()) {
+ // Parameter "startOfWeek" - if the time unit is the week, then pass value of parameter
+ // "startOfWeek" of "$dateDiff" expression, otherwise pass a valid default value, since
+ // "dateDiff" built-in function does not accept non-string type values for this
+ // parameter.
+ arguments.push_back(sbe::makeE<sbe::EIf>(
+ unitIsWeekRef.clone(), startOfWeekRef.clone(), makeConstant("sun"sv)));
+ }
// Set bindings for the frame.
bindings.push_back(std::move(startDateExpression));
bindings.push_back(std::move(endDateExpression));
bindings.push_back(std::move(unitExpression));
bindings.push_back(std::move(timezoneExpression));
+ if (expr->isStartOfWeekSpecified()) {
+ bindings.push_back(std::move(startOfWeekExpression));
+ bindings.push_back(generateIsEqualToStringCheck(unitRef, "week"sv));
+ }
// Create an expression to invoke built-in "dateDiff" function.
- auto dateDiffFunctionCall = sbe::makeE<sbe::EFunction>("dateDiff", std::move(arguments));
+ auto dateDiffFunctionCall = sbe::makeE<sbe::EFunction>("dateDiff"sv, std::move(arguments));
// Create expressions to check that each argument to "dateDiff" function exists, is not
// null, and is of the correct type.
- auto dateDiffExpression = buildMultiBranchConditional(
- // Return null if any of the parameters is either null or missing.
- generateReturnNullIfNullOrMissing(startDateRef),
- generateReturnNullIfNullOrMissing(endDateRef),
- generateReturnNullIfNullOrMissing(unitRef),
- generateReturnNullIfNullOrMissing(timezoneRef),
-
- // "timezone" parameter validation.
- CaseValuePair{
- generateNonStringCheck(timezoneRef),
- sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166504},
- "$dateDiff parameter 'timezone' must be a string")},
- CaseValuePair{
- makeNot(makeFunction(
- "isTimezone", sbe::makeE<sbe::EVariable>(timezoneDBSlot), timezoneRef.clone())),
- sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166505},
- "$dateDiff parameter 'timezone' must be a valid timezone")},
-
- // "startDate" parameter validation.
- generateFailIfNotCoercibleToDate(
- startDateRef, ErrorCodes::Error{5166500}, "$dateDiff"_sd, "startDate"_sd),
+ std::vector<CaseValuePair> inputValidationCases;
+
+ // Return null if any of the parameters is either null or missing.
+ inputValidationCases.push_back(generateReturnNullIfNullOrMissing(startDateRef));
+ inputValidationCases.push_back(generateReturnNullIfNullOrMissing(endDateRef));
+ inputValidationCases.push_back(generateReturnNullIfNullOrMissing(unitRef));
+ inputValidationCases.push_back(generateReturnNullIfNullOrMissing(timezoneRef));
+ if (expr->isStartOfWeekSpecified()) {
+ inputValidationCases.emplace_back(
+ sbe::makeE<sbe::EPrimBinary>(sbe::EPrimBinary::logicAnd,
+ unitIsWeekRef.clone(),
+ generateNullOrMissing(startOfWeekRef)),
+ makeConstant(sbe::value::TypeTags::Null, 0));
+ }
- // "endDate" parameter validation.
- generateFailIfNotCoercibleToDate(
- endDateRef, ErrorCodes::Error{5166501}, "$dateDiff"_sd, "endDate"_sd),
+ // "timezone" parameter validation.
+ inputValidationCases.emplace_back(
+ generateNonStringCheck(timezoneRef),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166504},
+ "$dateDiff parameter 'timezone' must be a string"));
+ inputValidationCases.emplace_back(
+ makeNot(makeFunction(
+ "isTimezone", sbe::makeE<sbe::EVariable>(timezoneDBSlot), timezoneRef.clone())),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166505},
+ "$dateDiff parameter 'timezone' must be a valid timezone"));
+
+ // "startDate" parameter validation.
+ inputValidationCases.emplace_back(generateFailIfNotCoercibleToDate(
+ startDateRef, ErrorCodes::Error{5166500}, "$dateDiff"_sd, "startDate"_sd));
+
+ // "endDate" parameter validation.
+ inputValidationCases.emplace_back(generateFailIfNotCoercibleToDate(
+ endDateRef, ErrorCodes::Error{5166501}, "$dateDiff"_sd, "endDate"_sd));
+
+ // "unit" parameter validation.
+ inputValidationCases.emplace_back(
+ generateNonStringCheck(unitRef),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166502},
+ "$dateDiff parameter 'unit' must be a string"));
+ inputValidationCases.emplace_back(
+ makeNot(makeFunction("isTimeUnit", unitRef.clone())),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166503},
+ "$dateDiff parameter 'unit' must be a valid time unit"));
+
+ // "startOfWeek" parameter validation.
+ if (expr->isStartOfWeekSpecified()) {
+ // If 'timeUnit' value is equal to "week" then validate "startOfWeek" parameter.
+ inputValidationCases.emplace_back(
+ sbe::makeE<sbe::EPrimBinary>(sbe::EPrimBinary::logicAnd,
+ unitIsWeekRef.clone(),
+ generateNonStringCheck(startOfWeekRef)),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{5338801},
+ "$dateDiff parameter 'startOfWeek' must be a string"));
+ inputValidationCases.emplace_back(
+ sbe::makeE<sbe::EPrimBinary>(
+ sbe::EPrimBinary::logicAnd,
+ unitIsWeekRef.clone(),
+ makeNot(makeFunction("isDayOfWeek", startOfWeekRef.clone()))),
+ sbe::makeE<sbe::EFail>(
+ ErrorCodes::Error{5338802},
+ "$dateDiff parameter 'startOfWeek' must be a valid day of the week"));
+ }
- // "unit" parameter validation.
- CaseValuePair{generateNonStringCheck(unitRef),
- sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166502},
- "$dateDiff parameter 'unit' must be a string")},
- CaseValuePair{
- makeNot(makeFunction("isTimeUnit", unitRef.clone())),
- sbe::makeE<sbe::EFail>(ErrorCodes::Error{5166503},
- "$dateDiff parameter 'unit' must be a valid time unit")},
- std::move(dateDiffFunctionCall));
+ auto dateDiffExpression = buildMultiBranchConditionalFromCaseValuePairs(
+ std::move(inputValidationCases), std::move(dateDiffFunctionCall));
_context->pushExpr(sbe::makeE<sbe::ELocalBind>(
frameId, std::move(bindings), std::move(dateDiffExpression)));
}
@@ -2754,10 +2804,10 @@ private:
* expressionName - a name of an expression the parameter belongs to.
* parameterName - a name of the parameter corresponding to variable 'dateRef'.
*/
- CaseValuePair generateFailIfNotCoercibleToDate(const sbe::EVariable& dateRef,
- ErrorCodes::Error errorCode,
- StringData expressionName,
- StringData parameterName) {
+ static CaseValuePair generateFailIfNotCoercibleToDate(const sbe::EVariable& dateRef,
+ ErrorCodes::Error errorCode,
+ StringData expressionName,
+ StringData parameterName) {
return {makeNot(sbe::makeE<sbe::ETypeMatch>(dateRef.clone(), dateTypeMask())),
sbe::makeE<sbe::EFail>(errorCode,
str::stream()
@@ -2769,11 +2819,23 @@ private:
* Creates a CaseValuePair such that Null value is returned if a value of variable denoted by
* 'variable' is null or missing.
*/
- CaseValuePair generateReturnNullIfNullOrMissing(const sbe::EVariable& variable) {
+ static CaseValuePair generateReturnNullIfNullOrMissing(const sbe::EVariable& variable) {
return {generateNullOrMissing(variable), makeConstant(sbe::value::TypeTags::Null, 0)};
}
/**
+ * Creates a boolean expression to check if 'variable' is equal to string 'string'.
+ */
+ static std::unique_ptr<sbe::EExpression> generateIsEqualToStringCheck(
+ const sbe::EVariable& variable, std::string_view string) {
+ return sbe::makeE<sbe::EPrimBinary>(sbe::EPrimBinary::logicAnd,
+ makeFunction("isString", variable.clone()),
+ sbe::makeE<sbe::EPrimBinary>(sbe::EPrimBinary::eq,
+ variable.clone(),
+ makeConstant(string)));
+ }
+
+ /**
* Shared expression building logic for trignometric expressions to make sure the operand
* is numeric and is not null.
*/
diff --git a/src/mongo/db/query/sbe_stage_builder_helpers.cpp b/src/mongo/db/query/sbe_stage_builder_helpers.cpp
index 305324a2be2..9b4899df1d2 100644
--- a/src/mongo/db/query/sbe_stage_builder_helpers.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_helpers.cpp
@@ -41,6 +41,8 @@
#include "mongo/db/exec/sbe/stages/union.h"
#include "mongo/db/exec/sbe/stages/unwind.h"
#include "mongo/db/matcher/matcher_type_set.h"
+#include <iterator>
+#include <numeric>
namespace mongo::stage_builder {
@@ -171,6 +173,17 @@ std::unique_ptr<sbe::EExpression> buildMultiBranchConditional(
return defaultCase;
}
+std::unique_ptr<sbe::EExpression> buildMultiBranchConditionalFromCaseValuePairs(
+ std::vector<CaseValuePair> caseValuePairs, std::unique_ptr<sbe::EExpression> defaultValue) {
+ return std::accumulate(
+ std::make_reverse_iterator(std::make_move_iterator(caseValuePairs.end())),
+ std::make_reverse_iterator(std::make_move_iterator(caseValuePairs.begin())),
+ std::move(defaultValue),
+ [](auto&& expression, auto&& caseValuePair) {
+ return buildMultiBranchConditional(std::move(caseValuePair), std::move(expression));
+ });
+}
+
std::unique_ptr<sbe::PlanStage> makeLimitTree(std::unique_ptr<sbe::PlanStage> inputStage,
PlanNodeId planNodeId,
long long limit) {
diff --git a/src/mongo/db/query/sbe_stage_builder_helpers.h b/src/mongo/db/query/sbe_stage_builder_helpers.h
index 36da900927f..3f95b9c4c59 100644
--- a/src/mongo/db/query/sbe_stage_builder_helpers.h
+++ b/src/mongo/db/query/sbe_stage_builder_helpers.h
@@ -150,6 +150,13 @@ std::unique_ptr<sbe::EExpression> buildMultiBranchConditional(
std::unique_ptr<sbe::EExpression> defaultCase);
/**
+ * Converts a std::vector of CaseValuePairs into a chain of EIf expressions in the same manner as
+ * the 'buildMultiBranchConditional()' function.
+ */
+std::unique_ptr<sbe::EExpression> buildMultiBranchConditionalFromCaseValuePairs(
+ std::vector<CaseValuePair> caseValuePairs, std::unique_ptr<sbe::EExpression> defaultValue);
+
+/**
* Insert a limit stage on top of the 'input' stage.
*/
std::unique_ptr<sbe::PlanStage> makeLimitTree(std::unique_ptr<sbe::PlanStage> inputStage,