From 8a19a31598ffed4440df9c648ff457bfdf9e01e0 Mon Sep 17 00:00:00 2001 From: Melodee Li Date: Fri, 23 Oct 2020 19:17:09 +0000 Subject: SERVER-49982 Implement $dayOf* agg expressions in SBE --- .../aggregation/expressions/day_of_expressions.js | 103 ++++++++++++ jstests/libs/sbe_assert_error_override.js | 5 +- src/mongo/db/exec/sbe/SConscript | 2 + src/mongo/db/exec/sbe/expressions/expression.cpp | 3 + .../expressions/sbe_day_of_expressions_test.cpp | 169 ++++++++++++++++++++ src/mongo/db/exec/sbe/vm/datetime.cpp | 175 +++++++++++++++++++++ src/mongo/db/exec/sbe/vm/datetime.h | 56 +++++++ src/mongo/db/exec/sbe/vm/vm.cpp | 72 ++++----- src/mongo/db/exec/sbe/vm/vm.h | 27 ++++ src/mongo/db/query/datetime/date_time_support.cpp | 5 + src/mongo/db/query/datetime/date_time_support.h | 5 + .../db/query/sbe_stage_builder_expression.cpp | 99 ++++++++---- src/mongo/db/query/sbe_stage_builder_helpers.cpp | 18 +++ src/mongo/db/query/sbe_stage_builder_helpers.h | 13 ++ 14 files changed, 688 insertions(+), 64 deletions(-) create mode 100644 jstests/aggregation/expressions/day_of_expressions.js create mode 100644 src/mongo/db/exec/sbe/expressions/sbe_day_of_expressions_test.cpp create mode 100644 src/mongo/db/exec/sbe/vm/datetime.cpp create mode 100644 src/mongo/db/exec/sbe/vm/datetime.h diff --git a/jstests/aggregation/expressions/day_of_expressions.js b/jstests/aggregation/expressions/day_of_expressions.js new file mode 100644 index 00000000000..eb73f670352 --- /dev/null +++ b/jstests/aggregation/expressions/day_of_expressions.js @@ -0,0 +1,103 @@ +// Tests for the $dayOfYear, $dayOfMonth, and $dayOfWeek expressions. + +(function() { +"use strict"; + +load("jstests/aggregation/extras/utils.js"); // For assertErrorCode +load("jstests/libs/sbe_assert_error_override.js"); + +const coll = db.dayOfExpressions; + +// +// Basic tests. +// +coll.drop(); +assert.commandWorked(coll.insert([ + {date: ISODate('1960-01-02 03:04:05Z')}, + {date: ISODate('1970-01-01 00:00:00.000Z')}, + {date: ISODate('1980-05-20 12:53:64.834Z')}, + {date: ISODate('1999-12-31 00:00:00.000Z')}, +])); + +let res = coll.aggregate({ + $project: { + _id: 0, + dayOfYear: {$dayOfYear: "$date"}, + dayOfMonth: {$dayOfMonth: "$date"}, + dayOfWeek: {$dayOfWeek: "$date"}, + } + }) + .toArray(); +assert(arrayEq(res, [ + {dayOfYear: 2, dayOfMonth: 2, dayOfWeek: 7}, + {dayOfYear: 1, dayOfMonth: 1, dayOfWeek: 5}, + {dayOfYear: 141, dayOfMonth: 20, dayOfWeek: 3}, + {dayOfYear: 365, dayOfMonth: 31, dayOfWeek: 6} +])); + +// +// Test date and timestamp dayOfYear equality. +// +assert(coll.drop()); +assert.commandWorked(coll.insert([ + {_id: 1, date: new Timestamp(1341337661, 1)}, + {_id: 2, date: new Date(1341337661000)}, +])); + +res = coll.aggregate({ + $project: { + _id: 0, + dayOfYear: {$dayOfYear: '$date'}, + dayOfMonth: {$dayOfMonth: "$date"}, + dayOfWeek: {$dayOfWeek: "$date"}, + } + }) + .toArray(); +assert.eq(res[0], res[1]); + +// +// Basic tests with timezones. +// +assert(coll.drop()); +assert.commandWorked(coll.insert([ + {date: new Date("January 14, 2011"), timezone: "America/Chicago"}, + {date: new Date("January 14, 2011"), timezone: "UTC"}, + {date: ISODate("1998-11-07T00:00:00Z"), timezone: "-0400"}, +])); + +res = coll.aggregate({ + $project: { + _id: 0, + dayOfYear: {$dayOfYear: {date: "$date", timezone: "$timezone"}}, + dayOfMonth: {$dayOfMonth: {date: "$date", timezone: "$timezone"}}, + dayOfWeek: {$dayOfWeek: {date: "$date", timezone: "$timezone"}}, + } + }) + .toArray(); +assert(arrayEq(res, [ + {dayOfYear: 13, dayOfMonth: 13, dayOfWeek: 5}, + {dayOfYear: 14, dayOfMonth: 14, dayOfWeek: 6}, + {dayOfYear: 310, dayOfMonth: 6, dayOfWeek: 6} +])); + +// +// Error Code tests. +// +let pipeline = {$project: {dayOfYear: {'$dayOfYear': {timezone: "$timezone"}}}}; +assertErrorCode(coll, pipeline, 40539); + +pipeline = { + $project: {dayOfYear: {'$dayOfYear': {date: 42}}} +}; +assertErrorCode(coll, pipeline, 16006); + +pipeline = { + $project: {date: {'$dayOfYear': {date: "$date", "timezone": 5}}} +}; +assertErrorCode(coll, pipeline, 40533); + +pipeline = { + $project: {date: {'$dayOfYear': {date: "$date", "timezone": "DoesNot/Exist"}}} +}; +assertErrorCode(coll, pipeline, 40485); +})(); diff --git a/jstests/libs/sbe_assert_error_override.js b/jstests/libs/sbe_assert_error_override.js index 5ebfa28410f..cdfd48ed517 100644 --- a/jstests/libs/sbe_assert_error_override.js +++ b/jstests/libs/sbe_assert_error_override.js @@ -22,7 +22,7 @@ // are discovered, they should be added to this list. const equivalentErrorCodesList = [ [28651, 5073201], - [16006, 4997703], + [16006, 4997703, 4998202], [28689, 5126701], [28690, 5126702], [28691, 5126703], @@ -50,12 +50,13 @@ const equivalentErrorCodesList = [ [40094, 5075301, 5075302], [40096, 5075303, 5075305], [40097, 5075304, 5075306], - [40485, 5075307, 4997704], + [40485, 5075307, 4997704, 4998201], [40515, 4848979], [40517, 4848980, 4997701], [40521, 4997702], [40522, 4997700], [40523, 4848972], + [40533, 4998200], ]; // This map is generated based on the contents of 'equivalentErrorCodesList'. This map should _not_ diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript index 7307772cde0..f2d9ba6a87a 100644 --- a/src/mongo/db/exec/sbe/SConscript +++ b/src/mongo/db/exec/sbe/SConscript @@ -54,6 +54,7 @@ sbeEnv.Library( 'util/debug_print.cpp', 'values/slot.cpp', 'vm/arith.cpp', + 'vm/datetime.cpp', 'vm/vm.cpp', ], LIBDEPS=[ @@ -104,6 +105,7 @@ env.CppUnitTest( 'expressions/sbe_coerce_to_string_test.cpp', 'expressions/sbe_concat_test.cpp', 'expressions/sbe_date_to_parts_test.cpp', + 'expressions/sbe_day_of_expressions_test.cpp', 'expressions/sbe_get_element_builtin_test.cpp', 'expressions/sbe_index_of_test.cpp', 'expressions/sbe_is_member_builtin_test.cpp', diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp index 1fc6504b2b0..4cdc18566a9 100644 --- a/src/mongo/db/exec/sbe/expressions/expression.cpp +++ b/src/mongo/db/exec/sbe/expressions/expression.cpp @@ -353,6 +353,9 @@ static stdx::unordered_map kBuiltinFunctions = { BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::dateToParts, false}}, {"isoDateToParts", BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::isoDateToParts, false}}, + {"dayOfYear", BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::dayOfYear, false}}, + {"dayOfMonth", BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::dayOfMonth, false}}, + {"dayOfWeek", BuiltinFn{[](size_t n) { return n == 3; }, vm::Builtin::dayOfWeek, false}}, {"datePartsWeekYear", BuiltinFn{[](size_t n) { return n == 9; }, vm::Builtin::datePartsWeekYear, false}}, {"split", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::split, false}}, diff --git a/src/mongo/db/exec/sbe/expressions/sbe_day_of_expressions_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_day_of_expressions_test.cpp new file mode 100644 index 00000000000..15c741510e0 --- /dev/null +++ b/src/mongo/db/exec/sbe/expressions/sbe_day_of_expressions_test.cpp @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/bson/oid.h" +#include "mongo/db/exec/sbe/expression_test_base.h" +#include "mongo/db/query/datetime/date_time_support.h" + +namespace mongo::sbe { + +class SBEDayOfExpressionTest : public EExpressionTestFixture { +public: + void runAndAssertNothing(const vm::CodeFragment* compiledExpr) { + auto [runTag, runVal] = runCompiledExpression(compiledExpr); + value::ValueGuard guard(runTag, runVal); + ASSERT_EQUALS(runTag, sbe::value::TypeTags::Nothing); + ASSERT_EQUALS(runVal, 0); + } + + void runAndAssertExpression(const vm::CodeFragment* compiledExpr, + int32_t dayOfExpressionExpected) { + auto [runTag, runVal] = runCompiledExpression(compiledExpr); + value::ValueGuard guard(runTag, runVal); + + ASSERT_EQUALS(value::TypeTags::NumberInt32, runTag); + ASSERT_EQUALS(dayOfExpressionExpected, value::bitcastTo(runVal)); + } +}; + +TEST_F(SBEDayOfExpressionTest, BasicDayOfYear) { + value::OwnedValueAccessor timezoneDBAccessor; + auto timezoneDBSlot = bindAccessor(&timezoneDBAccessor); + value::OwnedValueAccessor dateAccessor; + auto dateSlot = bindAccessor(&dateAccessor); + value::OwnedValueAccessor timezoneAccessor; + auto timezoneSlot = bindAccessor(&timezoneAccessor); + + auto dayOfYearExpr = sbe::makeE("dayOfYear", + sbe::makeEs(makeE(timezoneDBSlot), + makeE(dateSlot), + makeE(timezoneSlot))); + auto compiledDayOfYear = compileExpression(*dayOfYearExpr); + + // Test $dayOfYear returns the correct value. + auto tzdb = std::make_unique(); + timezoneDBAccessor.reset( + false, value::TypeTags::timeZoneDB, value::bitcastFrom(tzdb.get())); + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + auto [timezoneTag, timezoneVal] = value::makeNewString("UTC"); + timezoneAccessor.reset(timezoneTag, timezoneVal); + runAndAssertExpression(compiledDayOfYear.get(), 1); + + // Test $dayOfYear returns Nothing with invalid date. + dateAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfYear.get()); + + // Test $dayOfYear returns Nothing with invalid timezone. + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + timezoneAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfYear.get()); + + // Test $dayOfYear returns Nothing with invalid timezoneDB. + timezoneAccessor.reset(timezoneTag, timezoneVal); + timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfYear.get()); +} + +TEST_F(SBEDayOfExpressionTest, BasicDayOfMonth) { + value::OwnedValueAccessor timezoneDBAccessor; + auto timezoneDBSlot = bindAccessor(&timezoneDBAccessor); + value::OwnedValueAccessor dateAccessor; + auto dateSlot = bindAccessor(&dateAccessor); + value::OwnedValueAccessor timezoneAccessor; + auto timezoneSlot = bindAccessor(&timezoneAccessor); + + auto dayOfMonthExpr = sbe::makeE("dayOfMonth", + sbe::makeEs(makeE(timezoneDBSlot), + makeE(dateSlot), + makeE(timezoneSlot))); + auto compiledDayOfMonth = compileExpression(*dayOfMonthExpr); + + // Test $dayOfMonth returns the correct value. + auto tzdb = std::make_unique(); + timezoneDBAccessor.reset( + false, value::TypeTags::timeZoneDB, value::bitcastFrom(tzdb.get())); + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + auto [timezoneTag, timezoneVal] = value::makeNewString("UTC"); + timezoneAccessor.reset(timezoneTag, timezoneVal); + runAndAssertExpression(compiledDayOfMonth.get(), 1); + + // Test $dayOfMonth returns Nothing with invalid date. + dateAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfMonth.get()); + + // Test $dayOfMonth returns Nothing with invalid timezone. + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + timezoneAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfMonth.get()); + + // Test $dayOfMonth returns Nothing with invalid timezoneDB. + timezoneAccessor.reset(timezoneTag, timezoneVal); + timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfMonth.get()); +} + +TEST_F(SBEDayOfExpressionTest, BasicDayOfWeek) { + value::OwnedValueAccessor timezoneDBAccessor; + auto timezoneDBSlot = bindAccessor(&timezoneDBAccessor); + value::OwnedValueAccessor dateAccessor; + auto dateSlot = bindAccessor(&dateAccessor); + value::OwnedValueAccessor timezoneAccessor; + auto timezoneSlot = bindAccessor(&timezoneAccessor); + + auto dayOfWeekExpr = sbe::makeE("dayOfWeek", + sbe::makeEs(makeE(timezoneDBSlot), + makeE(dateSlot), + makeE(timezoneSlot))); + auto compiledDayOfWeek = compileExpression(*dayOfWeekExpr); + + // Test $dayOfWeek returns the correct value. + auto tzdb = std::make_unique(); + timezoneDBAccessor.reset( + false, value::TypeTags::timeZoneDB, value::bitcastFrom(tzdb.get())); + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + auto [timezoneTag, timezoneVal] = value::makeNewString("UTC"); + timezoneAccessor.reset(timezoneTag, timezoneVal); + runAndAssertExpression(compiledDayOfWeek.get(), 5); + + // Test $dayOfWeek returns Nothing with invalid date. + dateAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfWeek.get()); + + // Test $dayOfWeek returns Nothing with invalid timezone. + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom(21929999)); + timezoneAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfWeek.get()); + + // Test $dayOfWeek returns Nothing with invalid timezoneDB. + timezoneAccessor.reset(timezoneTag, timezoneVal); + timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom(42)); + runAndAssertNothing(compiledDayOfWeek.get()); +} + +} // namespace mongo::sbe diff --git a/src/mongo/db/exec/sbe/vm/datetime.cpp b/src/mongo/db/exec/sbe/vm/datetime.cpp new file mode 100644 index 00000000000..d0724fe56c5 --- /dev/null +++ b/src/mongo/db/exec/sbe/vm/datetime.cpp @@ -0,0 +1,175 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/exec/sbe/vm/vm.h" + +#include "mongo/db/exec/sbe/vm/datetime.h" +#include "mongo/db/query/datetime/date_time_support.h" +#include "mongo/util/represent_as.h" +#include "mongo/util/time_support.h" + +namespace mongo { +namespace sbe { +namespace vm { + +TimeZone getTimezone(value::TypeTags timezoneTag, + value::Value timezoneVal, + TimeZoneDatabase* timezoneDB) { + auto timezoneStr = value::getStringView(timezoneTag, timezoneVal); + if (timezoneStr == "") { + return timezoneDB->utcZone(); + } else { + return timezoneDB->getTimeZone(StringData{timezoneStr.data(), timezoneStr.size()}); + } +} + +Date_t getDate(value::TypeTags dateTag, value::Value dateVal) { + switch (dateTag) { + case value::TypeTags::Date: { + return Date_t::fromMillisSinceEpoch(value::bitcastTo(dateVal)); + } + case value::TypeTags::Timestamp: { + return Date_t::fromMillisSinceEpoch( + Timestamp(value::bitcastTo(dateVal)).getSecs() * 1000LL); + } + case value::TypeTags::ObjectId: { + auto objIdBuf = value::getObjectIdView(dateVal); + auto objId = OID::from(objIdBuf); + return objId.asDateT(); + } + case value::TypeTags::bsonObjectId: { + auto objIdBuf = value::getRawPointerView(dateVal); + auto objId = OID::from(objIdBuf); + return objId.asDateT(); + } + default: + MONGO_UNREACHABLE; + } +} + +namespace { + +/** + * The dayOfYear operation used by genericDayOfOp. + */ +struct DayOfYear { + static void doOperation(const Date_t& date, const TimeZone& timezone, int32_t& result) { + result = timezone.dayOfYear(date); + } +}; + +/** + * The dayOfMonth operation used by genericDayOfOp. + */ +struct DayOfMonth { + static void doOperation(const Date_t& date, const TimeZone& timezone, int32_t& result) { + result = timezone.dayOfMonth(date); + } +}; + +/** + * The dayOfWeek operation used by genericDayOfOp. + */ +struct DayOfWeek { + static void doOperation(const Date_t& date, const TimeZone& timezone, int32_t& result) { + result = timezone.dayOfWeek(date); + } +}; +} // namespace + +/** + * This is a simple dayOf operation templated by the Op parameter. + */ +template +std::tuple genericDayOfOp(value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue) { + // Get date. + if (dateTag != value::TypeTags::Date && dateTag != value::TypeTags::Timestamp && + dateTag != value::TypeTags::ObjectId && dateTag != value::TypeTags::bsonObjectId) { + return {false, value::TypeTags::Nothing, 0}; + } + auto date = getDate(dateTag, dateValue); + + if (timezoneDBTag != value::TypeTags::timeZoneDB) { + return {false, value::TypeTags::Nothing, 0}; + } + auto timezoneDB = value::getTimeZoneDBView(timezoneDBValue); + + // Get timezone. + if (!value::isString(timezoneTag)) { + return {false, value::TypeTags::Nothing, 0}; + } + auto timezone = getTimezone(timezoneTag, timezoneValue, timezoneDB); + + int32_t result; + Op::doOperation(date, timezone, result); + + return {false, value::TypeTags::NumberInt32, value::bitcastTo(result)}; +} + +std::tuple ByteCode::genericDayOfYear( + value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue) { + return genericDayOfOp( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + +std::tuple ByteCode::genericDayOfMonth( + value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue) { + return genericDayOfOp( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + +std::tuple ByteCode::genericDayOfWeek( + value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue) { + return genericDayOfOp( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + +} // namespace vm +} // namespace sbe +} // namespace mongo diff --git a/src/mongo/db/exec/sbe/vm/datetime.h b/src/mongo/db/exec/sbe/vm/datetime.h new file mode 100644 index 00000000000..8e99d7e61a0 --- /dev/null +++ b/src/mongo/db/exec/sbe/vm/datetime.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/exec/sbe/values/slot.h" +#include "mongo/db/exec/sbe/values/value.h" +#include "mongo/db/query/datetime/date_time_support.h" + +namespace mongo { +namespace sbe { +namespace vm { + +/** + * Returns a TimeZone object representing the zone given by timezoneTag and timezoneVal, or UTC if + * it was not a recognized time zone. + */ +TimeZone getTimezone(value::TypeTags timezoneTag, + value::Value timezoneVal, + TimeZoneDatabase* timezoneDB); + +/** + * Returns a Date_t object representing the datetime given by dateTag and dateVal. + */ +Date_t getDate(value::TypeTags dateTag, value::Value dateVal); + +} // namespace vm + +} // namespace sbe +} // namespace mongo \ No newline at end of file diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp index 8848c87227e..4b2a0f9ce18 100644 --- a/src/mongo/db/exec/sbe/vm/vm.cpp +++ b/src/mongo/db/exec/sbe/vm/vm.cpp @@ -38,6 +38,7 @@ #include "mongo/bson/oid.h" #include "mongo/db/exec/sbe/values/bson.h" #include "mongo/db/exec/sbe/values/value.h" +#include "mongo/db/exec/sbe/vm/datetime.h" #include "mongo/db/query/datetime/date_time_support.h" #include "mongo/db/storage/key_string.h" #include "mongo/util/fail_point.h" @@ -1193,41 +1194,6 @@ std::tuple ByteCode::builtinDateWeekYear(ui timezoneTuple); } -TimeZone getTimezone(value::TypeTags timezoneTag, - value::Value timezoneVal, - TimeZoneDatabase* timezoneDB) { - auto timezoneStr = value::getStringView(timezoneTag, timezoneVal); - if (timezoneStr == "") { - return timezoneDB->utcZone(); - } else { - return timezoneDB->getTimeZone(StringData{timezoneStr.data(), timezoneStr.size()}); - } -} - -Date_t getDate(value::TypeTags dateTag, value::Value dateVal) { - switch (dateTag) { - case value::TypeTags::Date: { - return Date_t::fromMillisSinceEpoch(value::bitcastTo(dateVal)); - } - case value::TypeTags::Timestamp: { - return Date_t::fromMillisSinceEpoch( - Timestamp(value::bitcastTo(dateVal)).getSecs() * 1000LL); - } - case value::TypeTags::ObjectId: { - auto objIdBuf = value::getObjectIdView(dateVal); - auto objId = OID::from(objIdBuf); - return objId.asDateT(); - } - case value::TypeTags::bsonObjectId: { - auto objIdBuf = value::getRawPointerView(dateVal); - auto objId = OID::from(objIdBuf); - return objId.asDateT(); - } - default: - MONGO_UNREACHABLE; - } -} - std::tuple ByteCode::builtinDateToParts(uint8_t arity) { auto [timezoneDBOwn, timezoneDBTag, timezoneDBVal] = getFromStack(0); if (timezoneDBTag != value::TypeTags::timeZoneDB) { @@ -1304,6 +1270,36 @@ std::tuple ByteCode::builtinIsoDateToParts( return {true, dateObjTag, dateObjVal}; } +std::tuple ByteCode::builtinDayOfYear(uint8_t arity) { + invariant(arity == 3); + + auto [timezoneDBOwn, timezoneDBTag, timezoneDBValue] = getFromStack(0); + auto [dateOwn, dateTag, dateValue] = getFromStack(1); + auto [timezoneOwn, timezoneTag, timezoneValue] = getFromStack(2); + return genericDayOfYear( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + +std::tuple ByteCode::builtinDayOfMonth(uint8_t arity) { + invariant(arity == 3); + + auto [timezoneDBOwn, timezoneDBTag, timezoneDBValue] = getFromStack(0); + auto [dateOwn, dateTag, dateValue] = getFromStack(1); + auto [timezoneOwn, timezoneTag, timezoneValue] = getFromStack(2); + return genericDayOfMonth( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + +std::tuple ByteCode::builtinDayOfWeek(uint8_t arity) { + invariant(arity == 3); + + auto [timezoneDBOwn, timezoneDBTag, timezoneDBValue] = getFromStack(0); + auto [dateOwn, dateTag, dateValue] = getFromStack(1); + auto [timezoneOwn, timezoneTag, timezoneValue] = getFromStack(2); + return genericDayOfWeek( + timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue); +} + std::tuple ByteCode::builtinBitTestPosition(uint8_t arity) { invariant(arity == 3); @@ -1906,6 +1902,12 @@ std::tuple ByteCode::dispatchBuiltin(Builti return builtinDateToParts(arity); case Builtin::isoDateToParts: return builtinIsoDateToParts(arity); + case Builtin::dayOfYear: + return builtinDayOfYear(arity); + case Builtin::dayOfMonth: + return builtinDayOfMonth(arity); + case Builtin::dayOfWeek: + return builtinDayOfWeek(arity); case Builtin::split: return builtinSplit(arity); case Builtin::regexMatch: diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h index 124a82217e5..7e8997c8b9e 100644 --- a/src/mongo/db/exec/sbe/vm/vm.h +++ b/src/mongo/db/exec/sbe/vm/vm.h @@ -35,6 +35,8 @@ #include "mongo/db/exec/sbe/values/slot.h" #include "mongo/db/exec/sbe/values/value.h" +#include "mongo/db/exec/sbe/vm/datetime.h" +#include "mongo/db/query/datetime/date_time_support.h" namespace mongo { namespace sbe { @@ -178,6 +180,9 @@ enum class Builtin : uint8_t { dateParts, dateToParts, isoDateToParts, + dayOfYear, + dayOfMonth, + dayOfWeek, datePartsWeekYear, dropFields, newObj, @@ -495,11 +500,33 @@ private: std::tuple genericTanh(value::TypeTags operandTag, value::Value operandValue); + std::tuple genericDayOfYear(value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue); + std::tuple genericDayOfMonth(value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue); + std::tuple genericDayOfWeek(value::TypeTags timezoneDBTag, + value::Value timezoneDBValue, + value::TypeTags dateTag, + value::Value dateValue, + value::TypeTags timezoneTag, + value::Value timezoneValue); + std::tuple builtinSplit(uint8_t arity); std::tuple builtinDate(uint8_t arity); std::tuple builtinDateWeekYear(uint8_t arity); std::tuple builtinDateToParts(uint8_t arity); std::tuple builtinIsoDateToParts(uint8_t arity); + std::tuple builtinDayOfYear(uint8_t arity); + std::tuple builtinDayOfMonth(uint8_t arity); + std::tuple builtinDayOfWeek(uint8_t arity); std::tuple builtinRegexMatch(uint8_t arity); std::tuple builtinDropFields(uint8_t arity); std::tuple builtinNewObj(uint8_t arity); diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp index 9af89734beb..85a0fde53ac 100644 --- a/src/mongo/db/query/datetime/date_time_support.cpp +++ b/src/mongo/db/query/datetime/date_time_support.cpp @@ -535,6 +535,11 @@ int TimeZone::dayOfYear(Date_t date) const { return timelib_day_of_year(time->y, time->m, time->d) + 1; } +int TimeZone::dayOfMonth(Date_t date) const { + auto time = getTimelibTime(date); + return time->d; +} + int TimeZone::isoDayOfWeek(Date_t date) const { auto time = getTimelibTime(date); return timelib_iso_day_of_week(time->y, time->m, time->d); diff --git a/src/mongo/db/query/datetime/date_time_support.h b/src/mongo/db/query/datetime/date_time_support.h index a97a2f4b5fd..0bc4a779180 100644 --- a/src/mongo/db/query/datetime/date_time_support.h +++ b/src/mongo/db/query/datetime/date_time_support.h @@ -171,6 +171,11 @@ public: */ int dayOfYear(Date_t) const; + /** + * Returns the day of the month, ranging from 1 to 31. + */ + int dayOfMonth(Date_t) const; + /** * Returns the week number for a date as a number between 0 (the partial week that precedes the * first Sunday of the year) and 53. diff --git a/src/mongo/db/query/sbe_stage_builder_expression.cpp b/src/mongo/db/query/sbe_stage_builder_expression.cpp index 04c97fc023b..c5833248818 100644 --- a/src/mongo/db/query/sbe_stage_builder_expression.cpp +++ b/src/mongo/db/query/sbe_stage_builder_expression.cpp @@ -298,30 +298,8 @@ void generateStringCaseConversionExpression(ExpressionVisitorContext* _context, sbe::makeE(frameId, std::move(str), std::move(totalCaseConversionExpr))); } -/** - * Generates an EExpression that checks if the input expression is not a string, _assuming that - * it has already been verified to be neither null nor missing. - */ -std::unique_ptr generateNonStringCheck(const sbe::EVariable& var) { - return sbe::makeE( - sbe::EPrimUnary::logicNot, - sbe::makeE("isString", sbe::makeEs(var.clone()))); -} - -/** - * Generates an EExpression that checks whether the input expression is null, missing, or - * unable to be converted to the type NumberInt32. - */ -std::unique_ptr generateNullishOrNotRepresentableInt32Check( - const sbe::EVariable& var) { - auto numericConvert32 = - sbe::makeE(var.clone(), sbe::value::TypeTags::NumberInt32); - return sbe::makeE( - sbe::EPrimBinary::logicOr, - generateNullOrMissing(var), - sbe::makeE( - sbe::EPrimUnary::logicNot, - sbe::makeE("exists", sbe::makeEs(std::move(numericConvert32))))); +std::unique_ptr makeNot(std::unique_ptr e) { + return sbe::makeE(sbe::EPrimUnary::logicNot, std::move(e)); } void buildArrayAccessByConstantIndex(ExpressionVisitorContext* context, @@ -2137,13 +2115,13 @@ public: generateTrigonometricExpression("radiansToDegrees"); } void visit(ExpressionDayOfMonth* expr) final { - unsupportedExpression("$dayOfMonth"); + generateDayOfExpression("dayOfMonth", expr); } void visit(ExpressionDayOfWeek* expr) final { - unsupportedExpression("$dayOfWeek"); + generateDayOfExpression("dayOfWeek", expr); } void visit(ExpressionDayOfYear* expr) final { - unsupportedExpression("$dayOfYear"); + generateDayOfExpression("dayOfYear", expr); } void visit(ExpressionHour* expr) final { unsupportedExpression("$hour"); @@ -2344,6 +2322,73 @@ private: _context->pushExpr(resultExpr.extractExpr(), std::move(loopJoinStage)); } + void generateDayOfExpression(StringData exprName, Expression* expr) { + auto frameId = _context->frameIdGenerator->generate(); + std::vector> args; + std::vector> binds; + sbe::EVariable dateRef(frameId, 0); + sbe::EVariable timezoneRef(frameId, 1); + + auto children = expr->getChildren(); + invariant(children.size() == 2); + _context->ensureArity(children[1] ? 2 : 1); + + auto timezone = [&]() { + if (children[1]) { + return _context->popExpr(); + } + auto [utcTag, utcVal] = sbe::value::makeNewString("UTC"); + return sbe::makeE(utcTag, utcVal); + }(); + auto date = _context->popExpr(); + + auto timeZoneDBSlot = _context->runtimeEnvironment->getSlot("timeZoneDB"_sd); + args.push_back(sbe::makeE(timeZoneDBSlot)); + + // Add date to arguments. + uint32_t dateTypeMask = (getBSONTypeMask(sbe::value::TypeTags::Date) | + getBSONTypeMask(sbe::value::TypeTags::Timestamp) | + getBSONTypeMask(sbe::value::TypeTags::ObjectId) | + getBSONTypeMask(sbe::value::TypeTags::bsonObjectId)); + binds.push_back(std::move(date)); + args.push_back(dateRef.clone()); + + // Add timezone to arguments. + binds.push_back(std::move(timezone)); + args.push_back(timezoneRef.clone()); + + // Check that each argument exists, is not null, and is the correct type. + auto totalDayOfFunc = buildMultiBranchConditional( + CaseValuePair{generateNullOrMissing(timezoneRef), + sbe::makeE(sbe::value::TypeTags::Null, 0)}, + CaseValuePair{generateNonStringCheck(timezoneRef), + sbe::makeE(ErrorCodes::Error{4998200}, + str::stream() << "$" << exprName.toString() + << " timezone must be a string")}, + CaseValuePair{sbe::makeE( + sbe::EPrimUnary::logicNot, + sbe::makeE( + "isTimezone", + sbe::makeEs(sbe::makeE(timeZoneDBSlot), + timezoneRef.clone()))), + sbe::makeE(ErrorCodes::Error{4998201}, + str::stream() + << "$" << exprName.toString() + << " timezone must be a valid timezone")}, + CaseValuePair{generateNullOrMissing(dateRef), + sbe::makeE(sbe::value::TypeTags::Null, 0)}, + CaseValuePair{ + sbe::makeE( + sbe::EPrimUnary::logicNot, + sbe::makeE(dateRef.clone(), dateTypeMask)), + sbe::makeE(ErrorCodes::Error{4998202}, + str::stream() << "$" << exprName.toString() + << " date must have a format of a date")}, + sbe::makeE(exprName.toString(), std::move(args))); + _context->pushExpr( + sbe::makeE(frameId, std::move(binds), std::move(totalDayOfFunc))); + } + /** * 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 5d7624f494d..f0ca2f22ecf 100644 --- a/src/mongo/db/query/sbe_stage_builder_helpers.cpp +++ b/src/mongo/db/query/sbe_stage_builder_helpers.cpp @@ -100,6 +100,24 @@ std::unique_ptr generateNonObjectCheck(const sbe::EVariable& v sbe::makeE("isObject", sbe::makeEs(var.clone()))); } +std::unique_ptr generateNonStringCheck(const sbe::EVariable& var) { + return sbe::makeE( + sbe::EPrimUnary::logicNot, + sbe::makeE("isString", sbe::makeEs(var.clone()))); +} + +std::unique_ptr generateNullishOrNotRepresentableInt32Check( + const sbe::EVariable& var) { + auto numericConvert32 = + sbe::makeE(var.clone(), sbe::value::TypeTags::NumberInt32); + return sbe::makeE( + sbe::EPrimBinary::logicOr, + generateNullOrMissing(var), + sbe::makeE( + sbe::EPrimUnary::logicNot, + sbe::makeE("exists", sbe::makeEs(std::move(numericConvert32))))); +} + template <> std::unique_ptr buildMultiBranchConditional( std::unique_ptr defaultCase) { diff --git a/src/mongo/db/query/sbe_stage_builder_helpers.h b/src/mongo/db/query/sbe_stage_builder_helpers.h index 104dc16b48c..bf0e5d27157 100644 --- a/src/mongo/db/query/sbe_stage_builder_helpers.h +++ b/src/mongo/db/query/sbe_stage_builder_helpers.h @@ -85,6 +85,19 @@ std::unique_ptr generateNegativeCheck(const sbe::EVariable& va */ std::unique_ptr generateNonObjectCheck(const sbe::EVariable& var); +/** + * Generates an EExpression that checks if the input expression is not a string, _assuming that + * it has already been verified to be neither null nor missing. + */ +std::unique_ptr generateNonStringCheck(const sbe::EVariable& var); + +/** + * Generates an EExpression that checks whether the input expression is null, missing, or + * unable to be converted to the type NumberInt32. + */ +std::unique_ptr generateNullishOrNotRepresentableInt32Check( + const sbe::EVariable& var); + /** * A pair representing a 1) true/false condition and 2) the value that should be returned if that * condition evaluates to true. -- cgit v1.2.1