diff options
-rw-r--r-- | jstests/aggregation/expressions/date_to_parts.js | 6 | ||||
-rw-r--r-- | jstests/libs/sbe_assert_error_override.js | 7 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/expression.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/sbe_date_to_parts_test.cpp | 126 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/sbe_iso_date_to_parts_test.cpp | 127 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.cpp | 135 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.h | 6 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.h | 5 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_expression.cpp | 106 |
11 files changed, 522 insertions, 8 deletions
diff --git a/jstests/aggregation/expressions/date_to_parts.js b/jstests/aggregation/expressions/date_to_parts.js index 09df79a4cd6..60238c77196 100644 --- a/jstests/aggregation/expressions/date_to_parts.js +++ b/jstests/aggregation/expressions/date_to_parts.js @@ -1,9 +1,5 @@ -/** - * @tags: [ - * sbe_incompatible, - * ] - */ load("jstests/aggregation/extras/utils.js"); // For assertErrorCode +load("jstests/libs/sbe_assert_error_override.js"); (function() { "use strict"; diff --git a/jstests/libs/sbe_assert_error_override.js b/jstests/libs/sbe_assert_error_override.js index 7fe6cff1344..ed6fa7a0b26 100644 --- a/jstests/libs/sbe_assert_error_override.js +++ b/jstests/libs/sbe_assert_error_override.js @@ -22,6 +22,7 @@ // are discovered, they should be added to this list. const equivalentErrorCodesList = [ [28651, 5073201], + [16006, 4997703], [16020, 5066300], [16007, 5066300], [16608, 4848401], @@ -42,9 +43,11 @@ const equivalentErrorCodesList = [ [40094, 5075301, 5075302], [40096, 5075303, 5075305], [40097, 5075304, 5075306], - [40485, 5075307], + [40485, 5075307, 4997704], [40515, 4848979], - [40517, 4848980], + [40517, 4848980, 4997701], + [40521, 4997702], + [40522, 4997700], [40523, 4848972], ]; diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript index fd64dd23062..787797e83c2 100644 --- a/src/mongo/db/exec/sbe/SConscript +++ b/src/mongo/db/exec/sbe/SConscript @@ -90,6 +90,8 @@ env.CppUnitTest( 'expressions/sbe_bson_size_test.cpp', 'expressions/sbe_coerce_to_string_test.cpp', 'expressions/sbe_concat_test.cpp', + 'expressions/sbe_date_to_parts_test.cpp', + 'expressions/sbe_iso_date_to_parts_test.cpp', 'expressions/sbe_is_member_builtin_test.cpp', 'expressions/sbe_index_of_test.cpp', 'expressions/sbe_to_upper_to_lower_test.cpp', diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp index b752de92e2d..1d8d43c3299 100644 --- a/src/mongo/db/exec/sbe/expressions/expression.cpp +++ b/src/mongo/db/exec/sbe/expressions/expression.cpp @@ -349,6 +349,10 @@ struct BuiltinFn { */ static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = { {"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}}, + {"isoDateToParts", + BuiltinFn{[](size_t n) { return n == 3 || n == 4; }, vm::Builtin::isoDateToParts, 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}}, @@ -400,6 +404,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}}, + {"isTimezone", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::isTimezone, false}}, }; /** diff --git a/src/mongo/db/exec/sbe/expressions/sbe_date_to_parts_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_date_to_parts_test.cpp new file mode 100644 index 00000000000..117a8e86264 --- /dev/null +++ b/src/mongo/db/exec/sbe/expressions/sbe_date_to_parts_test.cpp @@ -0,0 +1,126 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 SBEDateToPartsTest : 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, + const int32_t& year, + const int32_t& month, + const int32_t& day, + const int32_t& hour, + const int32_t& minute, + const int32_t& second, + const int32_t& millisecond) { + auto [runTag, runVal] = runCompiledExpression(compiledExpr); + value::ValueGuard guard(runTag, runVal); + auto obj = value::getObjectView(runVal); + + auto [yearTag, yearVal] = obj->getField("year"); + ASSERT_EQUALS(yearTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(year, value::bitcastTo<int32_t>(yearVal)); + + auto [monthTag, monthVal] = obj->getField("month"); + ASSERT_EQUALS(monthTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(month, value::bitcastTo<int32_t>(monthVal)); + + auto [dayTag, dayVal] = obj->getField("day"); + ASSERT_EQUALS(dayTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(day, value::bitcastTo<int32_t>(dayVal)); + + auto [hourTag, hourVal] = obj->getField("hour"); + ASSERT_EQUALS(hourTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(hour, value::bitcastTo<int32_t>(hourVal)); + + auto [minuteTag, minuteVal] = obj->getField("minute"); + ASSERT_EQUALS(minuteTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(minute, value::bitcastTo<int32_t>(minuteVal)); + + auto [secondTag, secondVal] = obj->getField("second"); + ASSERT_EQUALS(secondTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(second, value::bitcastTo<int32_t>(secondVal)); + + auto [millisecondTag, millisecondVal] = obj->getField("millisecond"); + ASSERT_EQUALS(millisecondTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(millisecond, value::bitcastTo<int32_t>(millisecondVal)); + } +}; + +TEST_F(SBEDateToPartsTest, BasicDateToParts) { + value::OwnedValueAccessor timezoneDBAccessor; + auto timezoneDBSlot = bindAccessor(&timezoneDBAccessor); + value::OwnedValueAccessor dateAccessor; + auto dateSlot = bindAccessor(&dateAccessor); + value::OwnedValueAccessor timezoneAccessor; + auto timezoneSlot = bindAccessor(&timezoneAccessor); + + auto dateToPartsExpr = sbe::makeE<sbe::EFunction>("dateToParts", + sbe::makeEs(makeE<EVariable>(timezoneDBSlot), + makeE<EVariable>(dateSlot), + makeE<EVariable>(timezoneSlot))); + auto compiledDateToParts = compileExpression(*dateToPartsExpr); + + // Test $dateToParts returns the correct date parts. + TimeZoneDatabase* tzdb = new TimeZoneDatabase(); + timezoneDBAccessor.reset(value::TypeTags::timeZoneDB, + value::bitcastFrom<TimeZoneDatabase*>(tzdb)); + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(21929999)); + auto [timezoneTag, timezoneVal] = value::makeNewString("UTC"); + timezoneAccessor.reset(timezoneTag, timezoneVal); + runAndAssertExpression(compiledDateToParts.get(), 1970, 1, 1, 6, 5, 29, 999); + + // Test $dateToParts returns Nothing with invalid date. + dateAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledDateToParts.get()); + + // Test $dateToParts returns Nothing with invalid timezone. + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(21929999)); + timezoneAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledDateToParts.get()); + + // Test $dateToParts returns Nothing with invalid timezoneDB. + timezoneAccessor.reset(timezoneTag, timezoneVal); + timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledDateToParts.get()); + delete tzdb; +} + +} // namespace mongo::sbe diff --git a/src/mongo/db/exec/sbe/expressions/sbe_iso_date_to_parts_test.cpp b/src/mongo/db/exec/sbe/expressions/sbe_iso_date_to_parts_test.cpp new file mode 100644 index 00000000000..090bccf50f3 --- /dev/null +++ b/src/mongo/db/exec/sbe/expressions/sbe_iso_date_to_parts_test.cpp @@ -0,0 +1,127 @@ +/** + * 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 SBEIsoDateToPartsTest : 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, + const int32_t& isoWeekYear, + const int32_t& isoWeek, + const int32_t& isoDayOfWeek, + const int32_t& hour, + const int32_t& minute, + const int32_t& second, + const int32_t& millisecond) { + auto [runTag, runVal] = runCompiledExpression(compiledExpr); + value::ValueGuard guard(runTag, runVal); + auto obj = value::getObjectView(runVal); + + auto [isoWeekYearTag, isoWeekYearVal] = obj->getField("isoWeekYear"); + ASSERT_EQUALS(isoWeekYearTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(isoWeekYear, value::bitcastTo<int32_t>(isoWeekYearVal)); + + auto [isoWeekTag, isoWeekVal] = obj->getField("isoWeek"); + ASSERT_EQUALS(isoWeekTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(isoWeek, value::bitcastTo<int32_t>(isoWeekVal)); + + auto [isoDayOfWeekTag, isoDayOfWeekVal] = obj->getField("isoDayOfWeek"); + ASSERT_EQUALS(isoDayOfWeekTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(isoDayOfWeek, value::bitcastTo<int32_t>(isoDayOfWeekVal)); + + auto [hourTag, hourVal] = obj->getField("hour"); + ASSERT_EQUALS(hourTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(hour, value::bitcastTo<int32_t>(hourVal)); + + auto [minuteTag, minuteVal] = obj->getField("minute"); + ASSERT_EQUALS(minuteTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(minute, value::bitcastTo<int32_t>(minuteVal)); + + auto [secondTag, secondVal] = obj->getField("second"); + ASSERT_EQUALS(secondTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(second, value::bitcastTo<int32_t>(secondVal)); + + auto [millisecondTag, millisecondVal] = obj->getField("millisecond"); + ASSERT_EQUALS(millisecondTag, value::TypeTags::NumberInt32); + ASSERT_EQUALS(millisecond, value::bitcastTo<int32_t>(millisecondVal)); + } +}; + +TEST_F(SBEIsoDateToPartsTest, BasicIsoDateToParts) { + value::OwnedValueAccessor timezoneDBAccessor; + auto timezoneDBSlot = bindAccessor(&timezoneDBAccessor); + value::OwnedValueAccessor dateAccessor; + auto dateSlot = bindAccessor(&dateAccessor); + value::OwnedValueAccessor timezoneAccessor; + auto timezoneSlot = bindAccessor(&timezoneAccessor); + + auto isoDateToPartsExpr = + sbe::makeE<sbe::EFunction>("isoDateToParts", + sbe::makeEs(makeE<EVariable>(timezoneDBSlot), + makeE<EVariable>(dateSlot), + makeE<EVariable>(timezoneSlot))); + auto compiledIsoDateToParts = compileExpression(*isoDateToPartsExpr); + + // Test $DateToParts with iso8601 returns the correct date parts. + TimeZoneDatabase* tzdb = new TimeZoneDatabase(); + timezoneDBAccessor.reset(value::TypeTags::timeZoneDB, + value::bitcastFrom<TimeZoneDatabase*>(tzdb)); + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(21929999)); + auto [timezoneTag, timezoneVal] = value::makeNewString("UTC"); + timezoneAccessor.reset(timezoneTag, timezoneVal); + runAndAssertExpression(compiledIsoDateToParts.get(), 1970, 1, 4, 6, 5, 29, 999); + + // Test $DateToParts with iso8601 flag and invalid date returns Nothing. + dateAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledIsoDateToParts.get()); + + // Test $DateToParts with iso8601 flag and invalid timezone returns Nothing. + dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(21929999)); + timezoneAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledIsoDateToParts.get()); + + // Test $DateToParts with iso8601 flag and invalid timezoneDB returns Nothing. + timezoneAccessor.reset(timezoneTag, timezoneVal); + timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(42)); + runAndAssertNothing(compiledIsoDateToParts.get()); + delete tzdb; +} + +} // namespace mongo::sbe diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp index f2f8dfb6d94..738ccb4ed92 100644 --- a/src/mongo/db/exec/sbe/vm/vm.cpp +++ b/src/mongo/db/exec/sbe/vm/vm.cpp @@ -35,6 +35,7 @@ #include <boost/algorithm/string.hpp> #include <pcrecpp.h> +#include "mongo/bson/oid.h" #include "mongo/db/exec/sbe/values/bson.h" #include "mongo/db/exec/sbe/values/value.h" #include "mongo/db/query/datetime/date_time_support.h" @@ -1146,6 +1147,117 @@ std::tuple<bool, value::TypeTags, value::Value> 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<int64_t>(dateVal)); + } + case value::TypeTags::Timestamp: { + return Date_t::fromMillisSinceEpoch( + Timestamp(value::bitcastTo<uint64_t>(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<bool, value::TypeTags, value::Value> ByteCode::builtinDateToParts(uint8_t arity) { + auto [timezoneDBOwn, timezoneDBTag, timezoneDBVal] = getFromStack(0); + if (timezoneDBTag != value::TypeTags::timeZoneDB) { + return {false, value::TypeTags::Nothing, 0}; + } + auto timezoneDB = value::getTimeZoneDBView(timezoneDBVal); + auto [dateOwn, dateTag, dateVal] = getFromStack(1); + + // Get timezone. + auto [timezoneOwn, timezoneTag, timezoneVal] = getFromStack(2); + if (!value::isString(timezoneTag)) { + return {false, value::TypeTags::Nothing, 0}; + } + TimeZone timezone = getTimezone(timezoneTag, timezoneVal, timezoneDB); + + // 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}; + } + Date_t date = getDate(dateTag, dateVal); + + // Get date parts. + auto dateParts = timezone.dateParts(date); + auto [dateObjTag, dateObjVal] = value::makeNewObject(); + value::ValueGuard guard{dateObjTag, dateObjVal}; + auto dateObj = value::getObjectView(dateObjVal); + dateObj->push_back("year", value::TypeTags::NumberInt32, dateParts.year); + dateObj->push_back("month", value::TypeTags::NumberInt32, dateParts.month); + dateObj->push_back("day", value::TypeTags::NumberInt32, dateParts.dayOfMonth); + dateObj->push_back("hour", value::TypeTags::NumberInt32, dateParts.hour); + dateObj->push_back("minute", value::TypeTags::NumberInt32, dateParts.minute); + dateObj->push_back("second", value::TypeTags::NumberInt32, dateParts.second); + dateObj->push_back("millisecond", value::TypeTags::NumberInt32, dateParts.millisecond); + guard.reset(); + return {true, dateObjTag, dateObjVal}; +} + +std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsoDateToParts(uint8_t arity) { + auto [timezoneDBOwn, timezoneDBTag, timezoneDBVal] = getFromStack(0); + if (timezoneDBTag != value::TypeTags::timeZoneDB) { + return {false, value::TypeTags::Nothing, 0}; + } + auto timezoneDB = value::getTimeZoneDBView(timezoneDBVal); + auto [dateOwn, dateTag, dateVal] = getFromStack(1); + + // Get timezone. + auto [timezoneOwn, timezoneTag, timezoneVal] = getFromStack(2); + if (!value::isString(timezoneTag)) { + return {false, value::TypeTags::Nothing, 0}; + } + TimeZone timezone = getTimezone(timezoneTag, timezoneVal, timezoneDB); + + // 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}; + } + Date_t date = getDate(dateTag, dateVal); + + // Get date parts. + auto dateParts = timezone.dateIso8601Parts(date); + auto [dateObjTag, dateObjVal] = value::makeNewObject(); + value::ValueGuard guard{dateObjTag, dateObjVal}; + auto dateObj = value::getObjectView(dateObjVal); + dateObj->push_back("isoWeekYear", value::TypeTags::NumberInt32, dateParts.year); + dateObj->push_back("isoWeek", value::TypeTags::NumberInt32, dateParts.weekOfYear); + dateObj->push_back("isoDayOfWeek", value::TypeTags::NumberInt32, dateParts.dayOfWeek); + dateObj->push_back("hour", value::TypeTags::NumberInt32, dateParts.hour); + dateObj->push_back("minute", value::TypeTags::NumberInt32, dateParts.minute); + dateObj->push_back("second", value::TypeTags::NumberInt32, dateParts.second); + dateObj->push_back("millisecond", value::TypeTags::NumberInt32, dateParts.millisecond); + guard.reset(); + return {true, dateObjTag, dateObjVal}; +} + std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinBitTestPosition(uint8_t arity) { invariant(arity == 3); @@ -1603,6 +1715,23 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIndexOfCP(uint8 return {false, value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(-1)}; } +std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsTimezone(uint8_t arity) { + auto [timezoneDBOwn, timezoneDBTag, timezoneDBVal] = getFromStack(0); + if (timezoneDBTag != value::TypeTags::timeZoneDB) { + return {false, value::TypeTags::Nothing, 0}; + } + auto timezoneDB = value::getTimeZoneDBView(timezoneDBVal); + auto [timezoneOwn, timezoneTag, timezoneVal] = getFromStack(1); + if (!value::isString(timezoneTag)) { + return {false, value::TypeTags::Boolean, false}; + } + auto timezoneStr = value::getStringView(timezoneTag, timezoneVal); + if (timezoneDB->isTimeZoneIdentifier((StringData{timezoneStr.data(), timezoneStr.size()}))) { + return {false, value::TypeTags::Boolean, true}; + } + return {false, value::TypeTags::Boolean, false}; +} + std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builtin f, uint8_t arity) { switch (f) { @@ -1610,6 +1739,10 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti return builtinDate(arity); case Builtin::datePartsWeekYear: return builtinDateWeekYear(arity); + case Builtin::dateToParts: + return builtinDateToParts(arity); + case Builtin::isoDateToParts: + return builtinIsoDateToParts(arity); case Builtin::split: return builtinSplit(arity); case Builtin::regexMatch: @@ -1694,6 +1827,8 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builti return builtinIndexOfBytes(arity); case Builtin::indexOfCP: return builtinIndexOfCP(arity); + case Builtin::isTimezone: + return builtinIsTimezone(arity); } MONGO_UNREACHABLE; diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h index 3654c9d1190..41081cba611 100644 --- a/src/mongo/db/exec/sbe/vm/vm.h +++ b/src/mongo/db/exec/sbe/vm/vm.h @@ -175,6 +175,8 @@ enum class Builtin : uint8_t { split, regexMatch, dateParts, + dateToParts, + isoDateToParts, datePartsWeekYear, dropFields, newObj, @@ -216,6 +218,7 @@ enum class Builtin : uint8_t { isMember, indexOfBytes, indexOfCP, + isTimezone, }; class CodeFragment { @@ -490,6 +493,8 @@ private: std::tuple<bool, value::TypeTags, value::Value> builtinSplit(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinDate(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinDateWeekYear(uint8_t arity); + std::tuple<bool, value::TypeTags, value::Value> builtinDateToParts(uint8_t arity); + std::tuple<bool, value::TypeTags, value::Value> builtinIsoDateToParts(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinRegexMatch(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinDropFields(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinNewObj(uint8_t arity); @@ -531,6 +536,7 @@ private: std::tuple<bool, value::TypeTags, value::Value> builtinIsMember(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinIndexOfBytes(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> builtinIndexOfCP(uint8_t arity); + std::tuple<bool, value::TypeTags, value::Value> builtinIsTimezone(uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> dispatchBuiltin(Builtin f, uint8_t arity); std::tuple<bool, value::TypeTags, value::Value> getFromStack(size_t offset) { diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp index 199a3100453..a0c56878a89 100644 --- a/src/mongo/db/query/datetime/date_time_support.cpp +++ b/src/mongo/db/query/datetime/date_time_support.cpp @@ -347,6 +347,11 @@ boost::optional<Seconds> TimeZoneDatabase::parseUtcOffset(StringData offsetSpec) return boost::none; } +bool TimeZoneDatabase::isTimeZoneIdentifier(StringData timeZoneId) const { + return (_timeZones.find(timeZoneId) != _timeZones.end()) || + static_cast<bool>(parseUtcOffset(timeZoneId)); +} + TimeZone TimeZoneDatabase::getTimeZone(StringData timeZoneId) const { auto tz = _timeZones.find(timeZoneId); if (tz != _timeZones.end()) { diff --git a/src/mongo/db/query/datetime/date_time_support.h b/src/mongo/db/query/datetime/date_time_support.h index 8035c9239d5..a97a2f4b5fd 100644 --- a/src/mongo/db/query/datetime/date_time_support.h +++ b/src/mongo/db/query/datetime/date_time_support.h @@ -421,6 +421,11 @@ public: static TimeZone utcZone(); /** + * Returns a boolean based on if 'timeZoneId' represents a valid timezone. + */ + bool isTimeZoneIdentifier(StringData timeZoneId) const; + + /** * Returns a TimeZone object representing the zone given by 'timeZoneId', or boost::none if it * was not a recognized time zone. */ diff --git a/src/mongo/db/query/sbe_stage_builder_expression.cpp b/src/mongo/db/query/sbe_stage_builder_expression.cpp index e9989044064..5f1f84f04cc 100644 --- a/src/mongo/db/query/sbe_stage_builder_expression.cpp +++ b/src/mongo/db/query/sbe_stage_builder_expression.cpp @@ -1606,8 +1606,112 @@ public: _context->pushExpr(sbe::makeE<sbe::ELocalBind>( frameId, std::move(operands), std::move(computeDateOrNull))); } + void visit(ExpressionDateToParts* expr) final { - unsupportedExpression("$dateFromString"); + auto frameId = _context->frameIdGenerator->generate(); + auto children = expr->getChildren(); + std::unique_ptr<sbe::EExpression> date, timezone, isoflag; + std::unique_ptr<sbe::EExpression> totalExprDateToParts; + std::vector<std::unique_ptr<sbe::EExpression>> args; + std::vector<std::unique_ptr<sbe::EExpression>> isoargs; + std::vector<std::unique_ptr<sbe::EExpression>> operands; + sbe::EVariable dateRef(frameId, 0); + sbe::EVariable timezoneRef(frameId, 1); + sbe::EVariable isoflagRef(frameId, 2); + + // Initialize arguments with values from stack or default values. + if (children[2]) { + isoflag = _context->popExpr(); + } else { + isoflag = sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Boolean, false); + } + if (children[1]) { + timezone = _context->popExpr(); + } else { + auto [utcTag, utcVal] = sbe::value::makeNewString("UTC"); + timezone = sbe::makeE<sbe::EConstant>(utcTag, utcVal); + } + if (children[0]) { + date = _context->popExpr(); + } else { + _context->pushExpr(sbe::makeE<sbe::EFail>(ErrorCodes::Error{4997700}, + "$dateToParts must include a date")); + return; + } + + // Add timezoneDB to arguments. + args.push_back( + sbe::makeE<sbe::EVariable>(_context->runtimeEnvironment->getSlot("timeZoneDB"_sd))); + isoargs.push_back( + sbe::makeE<sbe::EVariable>(_context->runtimeEnvironment->getSlot("timeZoneDB"_sd))); + + // 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)); + operands.push_back(std::move(date)); + args.push_back(dateRef.clone()); + isoargs.push_back(dateRef.clone()); + + // Add timezone to arguments. + operands.push_back(std::move(timezone)); + args.push_back(timezoneRef.clone()); + isoargs.push_back(timezoneRef.clone()); + + // Add iso8601 to arguments. + uint32_t isoTypeMask = getBSONTypeMask(sbe::value::TypeTags::Boolean); + operands.push_back(std::move(isoflag)); + args.push_back(isoflagRef.clone()); + isoargs.push_back(isoflagRef.clone()); + + // Determine whether to call dateToParts or isoDateToParts. + auto checkIsoflagValue = buildMultiBranchConditional( + CaseValuePair{sbe::makeE<sbe::EPrimBinary>( + sbe::EPrimBinary::eq, + isoflagRef.clone(), + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Boolean, false)), + sbe::makeE<sbe::EFunction>("dateToParts", std::move(args))}, + sbe::makeE<sbe::EFunction>("isoDateToParts", std::move(isoargs))); + + // Check that each argument exists, is not null, and is the correct type. + auto totalDateToPartsFunc = buildMultiBranchConditional( + CaseValuePair{generateNullOrMissing(frameId, 1), + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Null, 0)}, + CaseValuePair{ + sbe::makeE<sbe::EPrimUnary>( + sbe::EPrimUnary::logicNot, + sbe::makeE<sbe::EFunction>("isString", sbe::makeEs(timezoneRef.clone()))), + sbe::makeE<sbe::EFail>(ErrorCodes::Error{4997701}, + "$dateToParts timezone must be a string")}, + CaseValuePair{ + sbe::makeE<sbe::EPrimUnary>( + sbe::EPrimUnary::logicNot, + sbe::makeE<sbe::EFunction>( + "isTimezone", + sbe::makeEs(sbe::makeE<sbe::EVariable>( + _context->runtimeEnvironment->getSlot("timeZoneDB"_sd)), + timezoneRef.clone()))), + sbe::makeE<sbe::EFail>(ErrorCodes::Error{4997704}, + "$dateToParts timezone must be a valid timezone")}, + CaseValuePair{generateNullOrMissing(frameId, 2), + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Null, 0)}, + CaseValuePair{sbe::makeE<sbe::EPrimUnary>( + sbe::EPrimUnary::logicNot, + sbe::makeE<sbe::ETypeMatch>(isoflagRef.clone(), isoTypeMask)), + sbe::makeE<sbe::EFail>(ErrorCodes::Error{4997702}, + "$dateToParts iso8601 must be a boolean")}, + CaseValuePair{generateNullOrMissing(frameId, 0), + sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::Null, 0)}, + CaseValuePair{ + sbe::makeE<sbe::EPrimUnary>( + sbe::EPrimUnary::logicNot, + sbe::makeE<sbe::ETypeMatch>(dateRef.clone(), dateTypeMask)), + sbe::makeE<sbe::EFail>(ErrorCodes::Error{4997703}, + "$dateToParts date must have the format of a date")}, + std::move(checkIsoflagValue)); + _context->pushExpr(sbe::makeE<sbe::ELocalBind>( + frameId, std::move(operands), std::move(totalDateToPartsFunc))); } void visit(ExpressionDateToString* expr) final { unsupportedExpression("$dateFromString"); |