summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelodee Li <melodeeli98@gmail.com>2020-10-23 19:17:09 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-11 02:43:04 +0000
commit8a19a31598ffed4440df9c648ff457bfdf9e01e0 (patch)
tree3c39e7c2892267da20c149daf11ba24cf994a022
parentc4f0c53602d42366c9c16b0f92482a35440c1828 (diff)
downloadmongo-8a19a31598ffed4440df9c648ff457bfdf9e01e0.tar.gz
SERVER-49982 Implement $dayOf* agg expressions in SBE
-rw-r--r--jstests/aggregation/expressions/day_of_expressions.js103
-rw-r--r--jstests/libs/sbe_assert_error_override.js5
-rw-r--r--src/mongo/db/exec/sbe/SConscript2
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.cpp3
-rw-r--r--src/mongo/db/exec/sbe/expressions/sbe_day_of_expressions_test.cpp169
-rw-r--r--src/mongo/db/exec/sbe/vm/datetime.cpp175
-rw-r--r--src/mongo/db/exec/sbe/vm/datetime.h56
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.cpp72
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h27
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp5
-rw-r--r--src/mongo/db/query/datetime/date_time_support.h5
-rw-r--r--src/mongo/db/query/sbe_stage_builder_expression.cpp99
-rw-r--r--src/mongo/db/query/sbe_stage_builder_helpers.cpp18
-rw-r--r--src/mongo/db/query/sbe_stage_builder_helpers.h13
14 files changed, 688 insertions, 64 deletions
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<std::string, BuiltinFn> 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
+ * <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 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<int32_t>(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<sbe::EFunction>("dayOfYear",
+ sbe::makeEs(makeE<EVariable>(timezoneDBSlot),
+ makeE<EVariable>(dateSlot),
+ makeE<EVariable>(timezoneSlot)));
+ auto compiledDayOfYear = compileExpression(*dayOfYearExpr);
+
+ // Test $dayOfYear returns the correct value.
+ auto tzdb = std::make_unique<TimeZoneDatabase>();
+ timezoneDBAccessor.reset(
+ false, value::TypeTags::timeZoneDB, value::bitcastFrom<TimeZoneDatabase*>(tzdb.get()));
+ dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(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<int64_t>(42));
+ runAndAssertNothing(compiledDayOfYear.get());
+
+ // Test $dayOfYear 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(compiledDayOfYear.get());
+
+ // Test $dayOfYear returns Nothing with invalid timezoneDB.
+ timezoneAccessor.reset(timezoneTag, timezoneVal);
+ timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(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<sbe::EFunction>("dayOfMonth",
+ sbe::makeEs(makeE<EVariable>(timezoneDBSlot),
+ makeE<EVariable>(dateSlot),
+ makeE<EVariable>(timezoneSlot)));
+ auto compiledDayOfMonth = compileExpression(*dayOfMonthExpr);
+
+ // Test $dayOfMonth returns the correct value.
+ auto tzdb = std::make_unique<TimeZoneDatabase>();
+ timezoneDBAccessor.reset(
+ false, value::TypeTags::timeZoneDB, value::bitcastFrom<TimeZoneDatabase*>(tzdb.get()));
+ dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(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<int64_t>(42));
+ runAndAssertNothing(compiledDayOfMonth.get());
+
+ // Test $dayOfMonth 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(compiledDayOfMonth.get());
+
+ // Test $dayOfMonth returns Nothing with invalid timezoneDB.
+ timezoneAccessor.reset(timezoneTag, timezoneVal);
+ timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(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<sbe::EFunction>("dayOfWeek",
+ sbe::makeEs(makeE<EVariable>(timezoneDBSlot),
+ makeE<EVariable>(dateSlot),
+ makeE<EVariable>(timezoneSlot)));
+ auto compiledDayOfWeek = compileExpression(*dayOfWeekExpr);
+
+ // Test $dayOfWeek returns the correct value.
+ auto tzdb = std::make_unique<TimeZoneDatabase>();
+ timezoneDBAccessor.reset(
+ false, value::TypeTags::timeZoneDB, value::bitcastFrom<TimeZoneDatabase*>(tzdb.get()));
+ dateAccessor.reset(value::TypeTags::Date, value::bitcastFrom<int64_t>(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<int64_t>(42));
+ runAndAssertNothing(compiledDayOfWeek.get());
+
+ // Test $dayOfWeek 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(compiledDayOfWeek.get());
+
+ // Test $dayOfWeek returns Nothing with invalid timezoneDB.
+ timezoneAccessor.reset(timezoneTag, timezoneVal);
+ timezoneDBAccessor.reset(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(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
+ * <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/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<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;
+ }
+}
+
+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 <typename Op>
+std::tuple<bool, value::TypeTags, value::Value> 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<int32_t>(result)};
+}
+
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericDayOfYear(
+ value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue) {
+ return genericDayOfOp<DayOfYear>(
+ timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue);
+}
+
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericDayOfMonth(
+ value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue) {
+ return genericDayOfOp<DayOfMonth>(
+ timezoneDBTag, timezoneDBValue, dateTag, dateValue, timezoneTag, timezoneValue);
+}
+
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericDayOfWeek(
+ value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue) {
+ return genericDayOfOp<DayOfWeek>(
+ 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
+ * <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.
+ */
+
+#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<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) {
@@ -1304,6 +1270,36 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinIsoDateToParts(
return {true, dateObjTag, dateObjVal};
}
+std::tuple<bool, value::TypeTags, value::Value> 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<bool, value::TypeTags, value::Value> 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<bool, value::TypeTags, value::Value> 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<bool, value::TypeTags, value::Value> ByteCode::builtinBitTestPosition(uint8_t arity) {
invariant(arity == 3);
@@ -1906,6 +1902,12 @@ std::tuple<bool, value::TypeTags, value::Value> 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<bool, value::TypeTags, value::Value> genericTanh(value::TypeTags operandTag,
value::Value operandValue);
+ std::tuple<bool, value::TypeTags, value::Value> genericDayOfYear(value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue);
+ std::tuple<bool, value::TypeTags, value::Value> genericDayOfMonth(value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue);
+ std::tuple<bool, value::TypeTags, value::Value> genericDayOfWeek(value::TypeTags timezoneDBTag,
+ value::Value timezoneDBValue,
+ value::TypeTags dateTag,
+ value::Value dateValue,
+ value::TypeTags timezoneTag,
+ value::Value timezoneValue);
+
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> builtinDayOfYear(uint8_t arity);
+ std::tuple<bool, value::TypeTags, value::Value> builtinDayOfMonth(uint8_t arity);
+ std::tuple<bool, value::TypeTags, value::Value> builtinDayOfWeek(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);
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
@@ -172,6 +172,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<sbe::ELocalBind>(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<sbe::EExpression> generateNonStringCheck(const sbe::EVariable& var) {
- return sbe::makeE<sbe::EPrimUnary>(
- sbe::EPrimUnary::logicNot,
- sbe::makeE<sbe::EFunction>("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<sbe::EExpression> generateNullishOrNotRepresentableInt32Check(
- const sbe::EVariable& var) {
- auto numericConvert32 =
- sbe::makeE<sbe::ENumericConvert>(var.clone(), sbe::value::TypeTags::NumberInt32);
- return sbe::makeE<sbe::EPrimBinary>(
- sbe::EPrimBinary::logicOr,
- generateNullOrMissing(var),
- sbe::makeE<sbe::EPrimUnary>(
- sbe::EPrimUnary::logicNot,
- sbe::makeE<sbe::EFunction>("exists", sbe::makeEs(std::move(numericConvert32)))));
+std::unique_ptr<sbe::EExpression> makeNot(std::unique_ptr<sbe::EExpression> e) {
+ return sbe::makeE<sbe::EPrimUnary>(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<std::unique_ptr<sbe::EExpression>> args;
+ std::vector<std::unique_ptr<sbe::EExpression>> 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<sbe::EConstant>(utcTag, utcVal);
+ }();
+ auto date = _context->popExpr();
+
+ auto timeZoneDBSlot = _context->runtimeEnvironment->getSlot("timeZoneDB"_sd);
+ args.push_back(sbe::makeE<sbe::EVariable>(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::EConstant>(sbe::value::TypeTags::Null, 0)},
+ CaseValuePair{generateNonStringCheck(timezoneRef),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{4998200},
+ str::stream() << "$" << exprName.toString()
+ << " timezone must be a string")},
+ CaseValuePair{sbe::makeE<sbe::EPrimUnary>(
+ sbe::EPrimUnary::logicNot,
+ sbe::makeE<sbe::EFunction>(
+ "isTimezone",
+ sbe::makeEs(sbe::makeE<sbe::EVariable>(timeZoneDBSlot),
+ timezoneRef.clone()))),
+ sbe::makeE<sbe::EFail>(ErrorCodes::Error{4998201},
+ str::stream()
+ << "$" << exprName.toString()
+ << " timezone must be a valid timezone")},
+ CaseValuePair{generateNullOrMissing(dateRef),
+ 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{4998202},
+ str::stream() << "$" << exprName.toString()
+ << " date must have a format of a date")},
+ sbe::makeE<sbe::EFunction>(exprName.toString(), std::move(args)));
+ _context->pushExpr(
+ sbe::makeE<sbe::ELocalBind>(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<sbe::EExpression> generateNonObjectCheck(const sbe::EVariable& v
sbe::makeE<sbe::EFunction>("isObject", sbe::makeEs(var.clone())));
}
+std::unique_ptr<sbe::EExpression> generateNonStringCheck(const sbe::EVariable& var) {
+ return sbe::makeE<sbe::EPrimUnary>(
+ sbe::EPrimUnary::logicNot,
+ sbe::makeE<sbe::EFunction>("isString", sbe::makeEs(var.clone())));
+}
+
+std::unique_ptr<sbe::EExpression> generateNullishOrNotRepresentableInt32Check(
+ const sbe::EVariable& var) {
+ auto numericConvert32 =
+ sbe::makeE<sbe::ENumericConvert>(var.clone(), sbe::value::TypeTags::NumberInt32);
+ return sbe::makeE<sbe::EPrimBinary>(
+ sbe::EPrimBinary::logicOr,
+ generateNullOrMissing(var),
+ sbe::makeE<sbe::EPrimUnary>(
+ sbe::EPrimUnary::logicNot,
+ sbe::makeE<sbe::EFunction>("exists", sbe::makeEs(std::move(numericConvert32)))));
+}
+
template <>
std::unique_ptr<sbe::EExpression> buildMultiBranchConditional(
std::unique_ptr<sbe::EExpression> 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
@@ -86,6 +86,19 @@ std::unique_ptr<sbe::EExpression> generateNegativeCheck(const sbe::EVariable& va
std::unique_ptr<sbe::EExpression> 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<sbe::EExpression> 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<sbe::EExpression> 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.
*/