diff options
author | Mindaugas Malinauskas <mindaugas.malinauskas@mongodb.com> | 2021-01-21 17:24:14 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-02-11 13:24:45 +0000 |
commit | e9b02873749f2331f1853d00e13c8a67b39bf53a (patch) | |
tree | f643cb0b752fa091a7f9853ec71a5dba9cfe5d7c | |
parent | 6d419110f2dbd9b07ee96a601356ddbe99628dc5 (diff) | |
download | mongo-e9b02873749f2331f1853d00e13c8a67b39bf53a.tar.gz |
SERVER-53388 Week start parameter for $dateDiff aggregation expression
-rw-r--r-- | jstests/aggregation/expressions/date_diff.js | 105 | ||||
-rw-r--r-- | jstests/libs/sbe_assert_error_override.js | 3 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expression_test_base.h | 6 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/expression.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/sbe_date_diff_test.cpp | 182 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.h | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 78 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 21 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_bm.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_date_test.cpp | 104 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.h | 37 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support_test.cpp | 72 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_expression.cpp | 146 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_helpers.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_helpers.h | 7 |
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, |