summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec
diff options
context:
space:
mode:
authorEric Cox <eric.cox@mongodb.com>2020-07-07 15:18:52 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-07-24 20:19:03 +0000
commitb9515a079627f792c910c8ed2bb683cc9b071ebd (patch)
treeba4f826742cedb7aa5bda6f3dbd41ae09136261e /src/mongo/db/exec
parentf69521a0ec5a98e91a38077704b3bdca8377ebb6 (diff)
downloadmongo-b9515a079627f792c910c8ed2bb683cc9b071ebd.tar.gz
SERVER-49122 Implement dateFromParts expression for date manipulation in SBE
Diffstat (limited to 'src/mongo/db/exec')
-rw-r--r--src/mongo/db/exec/sbe/SConscript1
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.cpp50
-rw-r--r--src/mongo/db/exec/sbe/expressions/expression.h30
-rw-r--r--src/mongo/db/exec/sbe/sbe_numeric_convert_test.cpp402
-rw-r--r--src/mongo/db/exec/sbe/values/value.cpp21
-rw-r--r--src/mongo/db/exec/sbe/values/value.h55
-rw-r--r--src/mongo/db/exec/sbe/vm/arith.cpp20
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.cpp157
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h9
9 files changed, 739 insertions, 6 deletions
diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript
index c2ebc742998..0becf154b6e 100644
--- a/src/mongo/db/exec/sbe/SConscript
+++ b/src/mongo/db/exec/sbe/SConscript
@@ -86,6 +86,7 @@ env.CppUnitTest(
source=[
'sbe_test.cpp',
'sbe_key_string_test.cpp',
+ 'sbe_numeric_convert_test.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
diff --git a/src/mongo/db/exec/sbe/expressions/expression.cpp b/src/mongo/db/exec/sbe/expressions/expression.cpp
index 62961a8abd2..dd5f4cc2f0a 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.cpp
+++ b/src/mongo/db/exec/sbe/expressions/expression.cpp
@@ -345,6 +345,9 @@ struct BuiltinFn {
* The map of recognized builtin functions.
*/
static stdx::unordered_map<std::string, BuiltinFn> kBuiltinFunctions = {
+ {"dateParts", BuiltinFn{[](size_t n) { return n == 9; }, vm::Builtin::dateParts, 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}},
{"regexMatch", BuiltinFn{[](size_t n) { return n == 2; }, vm::Builtin::regexMatch, false}},
{"dropFields", BuiltinFn{[](size_t n) { return n > 0; }, vm::Builtin::dropFields, false}},
@@ -608,6 +611,53 @@ std::vector<DebugPrinter::Block> EFail::debugPrint() const {
return ret;
}
+std::unique_ptr<EExpression> ENumericConvert::clone() const {
+ return std::make_unique<ENumericConvert>(_nodes[0]->clone(), _target);
+}
+
+std::unique_ptr<vm::CodeFragment> ENumericConvert::compile(CompileCtx& ctx) const {
+ auto code = std::make_unique<vm::CodeFragment>();
+
+ auto operand = _nodes[0]->compile(ctx);
+ code->append(std::move(operand));
+ code->appendNumericConvert(_target);
+
+ return code;
+}
+
+std::vector<DebugPrinter::Block> ENumericConvert::debugPrint() const {
+ std::vector<DebugPrinter::Block> ret;
+
+ DebugPrinter::addKeyword(ret, "convert");
+
+ ret.emplace_back("(");
+
+ DebugPrinter::addBlocks(ret, _nodes[0]->debugPrint());
+
+ ret.emplace_back(DebugPrinter::Block("`,"));
+
+ switch (_target) {
+ case value::TypeTags::NumberInt32:
+ ret.emplace_back("int32");
+ break;
+ case value::TypeTags::NumberInt64:
+ ret.emplace_back("int64");
+ break;
+ case value::TypeTags::NumberDouble:
+ ret.emplace_back("double");
+ break;
+ case value::TypeTags::NumberDecimal:
+ ret.emplace_back("decimal");
+ break;
+ default:
+ MONGO_UNREACHABLE;
+ break;
+ }
+
+ ret.emplace_back("`)");
+ return ret;
+}
+
value::SlotAccessor* CompileCtx::getAccessor(value::SlotId slot) {
for (auto it = correlated.rbegin(); it != correlated.rend(); ++it) {
if (it->first == slot) {
diff --git a/src/mongo/db/exec/sbe/expressions/expression.h b/src/mongo/db/exec/sbe/expressions/expression.h
index 774b739a67d..6c3e49773f4 100644
--- a/src/mongo/db/exec/sbe/expressions/expression.h
+++ b/src/mongo/db/exec/sbe/expressions/expression.h
@@ -351,5 +351,35 @@ private:
ErrorCodes::Error _code;
std::string _message;
};
+
+/**
+ * This is a numeric conversion expression. It supports both narrowing and widening conversion under
+ * no loss of precision. If a given conversion loses precision the expression results in Nothing.
+ * ENumericConvert can be instantiated for the following source to target tags,
+ *
+ * NumberInt32 -> NumberInt64, NumberInt32 -> NumberDouble, NumberInt32 -> NumberDecimal
+ * NumberInt64 -> NumberInt32, NumberInt64 -> NumberDouble, NumberInt64 -> NumberDecimal
+ * NumberDouble -> NumberInt32, NumberDouble -> NumberInt64, NumberDouble -> NumberDecimal
+ * NumberDecimal -> NumberInt32, NumberDecimal -> NumberInt64, NumberDecimal -> NumberDouble
+ */
+class ENumericConvert final : public EExpression {
+public:
+ ENumericConvert(std::unique_ptr<EExpression> source, value::TypeTags target) : _target(target) {
+ _nodes.emplace_back(std::move(source));
+ validateNodes();
+ invariant(
+ target == value::TypeTags::NumberInt32 || target == value::TypeTags::NumberInt64 ||
+ target == value::TypeTags::NumberDouble || target == value::TypeTags::NumberDecimal);
+ }
+
+ std::unique_ptr<EExpression> clone() const override;
+
+ std::unique_ptr<vm::CodeFragment> compile(CompileCtx& ctx) const override;
+
+ std::vector<DebugPrinter::Block> debugPrint() const override;
+
+private:
+ value::TypeTags _target;
+};
} // namespace sbe
} // namespace mongo
diff --git a/src/mongo/db/exec/sbe/sbe_numeric_convert_test.cpp b/src/mongo/db/exec/sbe/sbe_numeric_convert_test.cpp
new file mode 100644
index 00000000000..372b67fa5b6
--- /dev/null
+++ b/src/mongo/db/exec/sbe/sbe_numeric_convert_test.cpp
@@ -0,0 +1,402 @@
+/**
+ * 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/expressions/expression.h"
+#include "mongo/db/exec/sbe/stages/co_scan.h"
+#include "mongo/db/exec/sbe/values/value.h"
+#include "mongo/db/exec/sbe/vm/vm.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/scopeguard.h"
+
+
+namespace mongo::sbe {
+namespace test_detail {
+template <typename T>
+std::unique_ptr<sbe::EExpression> makeEFromNumber(const T in,
+ value::TypeTags sourceTag,
+ value::TypeTags targetTag) {
+ return makeE<ENumericConvert>(makeE<EConstant>(sourceTag, value::bitcastFrom(in)), targetTag);
+}
+
+template <>
+std::unique_ptr<sbe::EExpression> makeEFromNumber<mongo::Decimal128>(const Decimal128 in,
+ value::TypeTags sourceTag,
+ value::TypeTags targetTag) {
+ auto [tag, value] = sbe::value::makeCopyDecimal(in);
+ return makeE<ENumericConvert>(makeE<EConstant>(tag, value), targetTag);
+}
+} // namespace test_detail
+
+class SBENumericTest : public mongo::unittest::Test {
+protected:
+ // Assert that convert(input) == output.
+ template <typename Input, typename Output>
+ void assertConversion(const Input input,
+ const Output output,
+ const value::TypeTags srcTag,
+ const value::TypeTags targetTag) {
+
+ auto expr = test_detail::makeEFromNumber(input, srcTag, targetTag);
+
+ auto code = expr->compile(_ctx);
+
+ auto [owned, tag, val] = _interpreter.run(code.get());
+
+ auto guard = makeGuard([owned = owned, tag = tag, val = val] {
+ if (owned) {
+ value::releaseValue(tag, val);
+ }
+ });
+
+ ASSERT_EQUALS(tag, targetTag);
+
+ if constexpr (std::is_same_v<Output, Decimal128>) {
+ ASSERT(value::bitcastTo<Decimal128>(val).isEqual(output));
+ } else if constexpr (std::is_same_v<Output, double>) {
+ ASSERT_APPROX_EQUAL(
+ value::bitcastTo<Output>(val), output, std::numeric_limits<double>::epsilon());
+ } else {
+ ASSERT_EQUALS(value::bitcastTo<Output>(val), output);
+ }
+ }
+
+ // assert that a conversion is lossy.
+ template <typename T>
+ void assertLossy(const T input, const value::TypeTags srcTag, const value::TypeTags targetTag) {
+ auto expr = test_detail::makeEFromNumber(input, srcTag, targetTag);
+
+ auto code = expr->compile(_ctx);
+
+ auto [owned, tag, val] = _interpreter.run(code.get());
+
+ auto guard = makeGuard([owned = owned, tag = tag, val = val] {
+ if (owned) {
+ value::releaseValue(tag, val);
+ }
+ });
+
+ ASSERT_EQUALS(tag, value::TypeTags::Nothing);
+ }
+
+ CompileCtx _ctx;
+ CoScanStage _emptyStage;
+
+ vm::ByteCode _interpreter;
+};
+
+TEST_F(SBENumericTest, Int32ToInt64) {
+ assertConversion(int32_t{-2147483648},
+ int64_t{-2147483648},
+ value::TypeTags::NumberInt32,
+ value::TypeTags::NumberInt64);
+ assertConversion(
+ int32_t{-10}, int64_t{-10}, value::TypeTags::NumberInt32, value::TypeTags::NumberInt64);
+ assertConversion(
+ int32_t{0}, int64_t{0}, value::TypeTags::NumberInt32, value::TypeTags::NumberInt64);
+ assertConversion(
+ int32_t{-0}, int64_t{-0}, value::TypeTags::NumberInt32, value::TypeTags::NumberInt64);
+ assertConversion(
+ int32_t{10}, int64_t{10}, value::TypeTags::NumberInt32, value::TypeTags::NumberInt64);
+ assertConversion(int32_t{2147483647},
+ int64_t{2147483647},
+ value::TypeTags::NumberInt32,
+ value::TypeTags::NumberInt64);
+}
+
+TEST_F(SBENumericTest, Int32ToDouble) {
+ assertConversion(
+ int32_t{-2}, double{-2.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDouble);
+ assertConversion(
+ int32_t{-1}, double{-1.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDouble);
+ assertConversion(
+ int32_t{0}, double{0.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDouble);
+ assertConversion(
+ int32_t{-0}, double{-0.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDouble);
+ assertConversion(
+ int32_t{1}, double{1.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDouble);
+}
+
+TEST_F(SBENumericTest, Int32ToDecimal) {
+ assertConversion(int32_t{-2},
+ Decimal128{-2.0},
+ value::TypeTags::NumberInt32,
+ value::TypeTags::NumberDecimal);
+ assertConversion(int32_t{-1},
+ Decimal128{-1.0},
+ value::TypeTags::NumberInt32,
+ value::TypeTags::NumberDecimal);
+ assertConversion(
+ int32_t{0}, Decimal128{0.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDecimal);
+ assertConversion(int32_t{-0},
+ Decimal128{-0.0},
+ value::TypeTags::NumberInt32,
+ value::TypeTags::NumberDecimal);
+ assertConversion(
+ int32_t{1}, Decimal128{1.0}, value::TypeTags::NumberInt32, value::TypeTags::NumberDecimal);
+}
+
+TEST_F(SBENumericTest, Int64ToInt32) {
+ assertConversion(int64_t{-2147483648},
+ int32_t{-2147483648},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberInt32);
+ assertConversion(
+ int64_t{-10}, int32_t{-10}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt32);
+ assertConversion(
+ int64_t{0}, int32_t{0}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt32);
+ assertConversion(
+ int64_t{-0}, int32_t{-0}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt32);
+ assertConversion(
+ int64_t{10}, int32_t{10}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt32);
+ assertConversion(int64_t{2147483647},
+ int32_t{2147483647},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberInt32);
+}
+
+TEST_F(SBENumericTest, Int64ToInt64) {
+ assertConversion(int64_t{-2147483648},
+ int32_t{-2147483648},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberInt64);
+ assertConversion(
+ int64_t{-10}, int32_t{-10}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt64);
+ assertConversion(
+ int64_t{0}, int32_t{0}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt64);
+ assertConversion(
+ int64_t{-0}, int32_t{-0}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt64);
+ assertConversion(
+ int64_t{10}, int32_t{10}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt64);
+ assertConversion(int64_t{2147483647},
+ int32_t{2147483647},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberInt64);
+}
+
+TEST_F(SBENumericTest, Int64ToDouble) {
+ assertConversion(int64_t{-2147483649},
+ double{-2147483649.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{-10}, double{-10.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{-1}, double{-1.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{0}, double{0.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{-0}, double{-0.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{1}, double{1.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(
+ int64_t{10}, double{10.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDouble);
+ assertConversion(int64_t{2147483648},
+ double{2147483648.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDouble);
+}
+
+TEST_F(SBENumericTest, Int64ToDecimal) {
+ assertConversion(int64_t{-2147483649},
+ Decimal128{-2147483649.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+ assertConversion(int64_t{-10},
+ Decimal128{-10.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+ assertConversion(int64_t{-1},
+ Decimal128{-1.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+ assertConversion(
+ int64_t{0}, Decimal128{0.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDecimal);
+ assertConversion(int64_t{-0},
+ Decimal128{-0.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+ assertConversion(
+ int64_t{1}, Decimal128{1.0}, value::TypeTags::NumberInt64, value::TypeTags::NumberDecimal);
+ assertConversion(int64_t{10},
+ Decimal128{10.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+ assertConversion(int64_t{2147483648},
+ Decimal128{2147483648.0},
+ value::TypeTags::NumberInt64,
+ value::TypeTags::NumberDecimal);
+}
+
+TEST_F(SBENumericTest, Decimal128ToInt32) {
+ assertConversion(Decimal128{-2147483648.0},
+ int32_t{-2147483648},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt32);
+ assertConversion(Decimal128{-10.0},
+ int32_t{-10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt32);
+ assertConversion(
+ Decimal128{0.0}, int32_t{0}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt32);
+ assertConversion(
+ Decimal128{1.0}, int32_t{1}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt32);
+ assertConversion(Decimal128{10.0},
+ int32_t{10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt32);
+ assertConversion(Decimal128{2147483647.0},
+ int32_t{2147483647},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt32);
+}
+
+TEST_F(SBENumericTest, Decimal128ToInt64) {
+ assertConversion(Decimal128{-2147483649.0},
+ int64_t{-2147483649},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt64);
+ assertConversion(Decimal128{-10.0},
+ int64_t{-10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt64);
+ assertConversion(
+ Decimal128{0.0}, int64_t{0}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt64);
+ assertConversion(
+ Decimal128{1.0}, int64_t{1}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt64);
+ assertConversion(Decimal128{10.0},
+ int64_t{10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt64);
+ assertConversion(Decimal128{2147483648.0},
+ int64_t{2147483648},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberInt64);
+}
+
+TEST_F(SBENumericTest, Decimal128ToDouble) {
+ assertConversion(Decimal128{-2147483649.0},
+ double{-2147483649},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberDouble);
+ assertConversion(Decimal128{-10.0},
+ double{-10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberDouble);
+ assertConversion(
+ Decimal128{0.0}, double{0}, value::TypeTags::NumberDecimal, value::TypeTags::NumberDouble);
+ assertConversion(
+ Decimal128{1.0}, double{1}, value::TypeTags::NumberDecimal, value::TypeTags::NumberDouble);
+ assertConversion(Decimal128{10.0},
+ double{10},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberDouble);
+ assertConversion(Decimal128{2147483648.0},
+ double{2147483648},
+ value::TypeTags::NumberDecimal,
+ value::TypeTags::NumberDouble);
+}
+
+TEST_F(SBENumericTest, DoubleToInt32) {
+ assertConversion(double{-2147483648.0},
+ int32_t{-2147483648},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberInt32);
+ assertConversion(
+ double{-10.0}, int32_t{-10}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt32);
+ assertConversion(
+ double{0.0}, int32_t{0}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt32);
+ assertConversion(
+ double{1.0}, int32_t{1}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt32);
+ assertConversion(
+ double{10.0}, int32_t{10}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt32);
+ assertConversion(double{2147483647.0},
+ int32_t{2147483647},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberInt32);
+}
+
+TEST_F(SBENumericTest, DoubleToInt64) {
+ assertConversion(double{-2147483649.0},
+ int64_t{-2147483649},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberInt64);
+ assertConversion(
+ double{-10.0}, int64_t{-10}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+ assertConversion(
+ double{0.0}, int64_t{0}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+ assertConversion(
+ double{1.0}, int64_t{1}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+ assertConversion(
+ double{10.0}, int64_t{10}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+ assertConversion(
+ double{9999.0}, int64_t{9999}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+ assertConversion(double{2147483647.0},
+ int64_t{2147483647},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberInt64);
+}
+
+TEST_F(SBENumericTest, DoubleToDecimal) {
+ assertConversion(double{-2147483649.0},
+ Decimal128{-2147483649},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberDecimal);
+ assertConversion(double{-10.0},
+ Decimal128{-10},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberDecimal);
+ assertConversion(
+ double{0.0}, Decimal128{0}, value::TypeTags::NumberDouble, value::TypeTags::NumberDecimal);
+ assertConversion(
+ double{1.0}, Decimal128{1}, value::TypeTags::NumberDouble, value::TypeTags::NumberDecimal);
+ assertConversion(double{10.0},
+ Decimal128{10},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberDecimal);
+ assertConversion(double{9999.0},
+ Decimal128{9999},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberDecimal);
+ assertConversion(double{2147483647.0},
+ Decimal128{2147483647},
+ value::TypeTags::NumberDouble,
+ value::TypeTags::NumberDecimal);
+}
+
+TEST_F(SBENumericTest, LossyConvertsToNothing) {
+ assertLossy(int64_t{2147483648}, value::TypeTags::NumberInt64, value::TypeTags::NumberInt32);
+ assertLossy(Decimal128{0.1}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt32);
+ assertLossy(Decimal128{0.1}, value::TypeTags::NumberDecimal, value::TypeTags::NumberInt64);
+ assertLossy(
+ Decimal128{"1.9E308"}, value::TypeTags::NumberDecimal, value::TypeTags::NumberDouble);
+ assertLossy(double{1999.1}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt32);
+ assertLossy(double{1999.1}, value::TypeTags::NumberDouble, value::TypeTags::NumberInt64);
+}
+
+} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/values/value.cpp b/src/mongo/db/exec/sbe/values/value.cpp
index aa9efdeb687..f281518ca16 100644
--- a/src/mongo/db/exec/sbe/values/value.cpp
+++ b/src/mongo/db/exec/sbe/values/value.cpp
@@ -35,6 +35,7 @@
#include "mongo/db/exec/sbe/values/bson.h"
#include "mongo/db/exec/sbe/values/value_builder.h"
+#include "mongo/db/query/datetime/date_time_support.h"
#include "mongo/db/storage/key_string.h"
namespace mongo {
@@ -80,7 +81,7 @@ void releaseValue(TypeTags tag, Value val) noexcept {
delete getKeyStringView(val);
break;
case TypeTags::pcreRegex:
- delete getPrceRegexView(val);
+ delete getPcreRegexView(val);
break;
default:
break;
@@ -146,6 +147,13 @@ std::ostream& operator<<(std::ostream& os, const TypeTags tag) {
case TypeTags::bsonObjectId:
os << "bsonObjectId";
break;
+ case TypeTags::ksValue:
+ os << "KeyString";
+ case TypeTags::pcreRegex:
+ os << "pcreRegex";
+ case TypeTags::timeZoneDB:
+ os << "timeZoneDB";
+ break;
default:
os << "unknown tag";
break;
@@ -167,6 +175,9 @@ void printValue(std::ostream& os, TypeTags tag, Value val) {
case value::TypeTags::NumberDecimal:
os << bitcastTo<Decimal128>(val).toString();
break;
+ case value::TypeTags::Date:
+ os << bitcastTo<int64_t>(val);
+ break;
case value::TypeTags::Boolean:
os << ((val) ? "true" : "false");
break;
@@ -294,11 +305,17 @@ void printValue(std::ostream& os, TypeTags tag, Value val) {
break;
}
case value::TypeTags::pcreRegex: {
- auto regex = getPrceRegexView(val);
+ auto regex = getPcreRegexView(val);
// TODO: Also include the regex flags.
os << "/" << regex->pattern() << "/";
break;
}
+ case value::TypeTags::timeZoneDB: {
+ auto tzdb = getTimeZoneDBView(val);
+ auto timeZones = tzdb->getTimeZoneStrings();
+ os << "TimeZoneDatabase(" + timeZones.front() + "..." + timeZones.back() + ")";
+ break;
+ }
default:
MONGO_UNREACHABLE;
}
diff --git a/src/mongo/db/exec/sbe/values/value.h b/src/mongo/db/exec/sbe/values/value.h
index e4fdb27d510..46d6d78a09c 100644
--- a/src/mongo/db/exec/sbe/values/value.h
+++ b/src/mongo/db/exec/sbe/values/value.h
@@ -44,6 +44,7 @@
#include "mongo/bson/ordering.h"
#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util.h"
+#include "mongo/util/represent_as.h"
namespace pcrecpp {
class RE;
@@ -56,6 +57,9 @@ namespace mongo {
namespace KeyString {
class Value;
}
+
+class TimeZoneDatabase;
+
namespace sbe {
using FrameId = int64_t;
using SpoolId = int64_t;
@@ -71,7 +75,7 @@ enum class TypeTags : uint8_t {
// The value does not exist, aka Nothing in the Maybe monad.
Nothing = 0,
- // Numberical data types.
+ // Numerical data types.
NumberInt32,
NumberInt64,
NumberDouble,
@@ -104,6 +108,9 @@ enum class TypeTags : uint8_t {
// Pointer to a compiled PCRE regular expression object.
pcreRegex,
+
+ // Pointer to a timezone database object.
+ timeZoneDB,
};
std::ostream& operator<<(std::ostream& os, const TypeTags tag);
@@ -559,10 +566,14 @@ inline KeyString::Value* getKeyStringView(Value val) noexcept {
return reinterpret_cast<KeyString::Value*>(val);
}
-inline pcrecpp::RE* getPrceRegexView(Value val) noexcept {
+inline pcrecpp::RE* getPcreRegexView(Value val) noexcept {
return reinterpret_cast<pcrecpp::RE*>(val);
}
+inline TimeZoneDatabase* getTimeZoneDBView(Value val) noexcept {
+ return reinterpret_cast<TimeZoneDatabase*>(val);
+}
+
std::pair<TypeTags, Value> makeCopyKeyString(const KeyString::Value& inKey);
std::pair<TypeTags, Value> makeCopyPcreRegex(const pcrecpp::RE&);
@@ -620,7 +631,7 @@ inline std::pair<TypeTags, Value> copyValue(TypeTags tag, Value val) {
case TypeTags::ksValue:
return makeCopyKeyString(*getKeyStringView(val));
case TypeTags::pcreRegex:
- return makeCopyPcreRegex(*getPrceRegexView(val));
+ return makeCopyPcreRegex(*getPcreRegexView(val));
default:
break;
}
@@ -662,6 +673,44 @@ inline T numericCast(TypeTags tag, Value val) noexcept {
}
}
+/**
+ * Performs a lossless numeric conversion from a value to a destination type denoted by the target
+ * TypeTag. In the case that a conversion is lossy, we return Nothing.
+ */
+template <typename T>
+inline std::tuple<bool, value::TypeTags, value::Value> numericConvLossless(
+ T value, value::TypeTags targetTag) {
+ switch (targetTag) {
+ case value::TypeTags::NumberInt32: {
+ if (auto result = representAs<int32_t>(value); result) {
+ return {false, value::TypeTags::NumberInt32, value::bitcastFrom(*result)};
+ }
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ case value::TypeTags::NumberInt64: {
+ if (auto result = representAs<int64_t>(value); result) {
+ return {false, value::TypeTags::NumberInt64, value::bitcastFrom(*result)};
+ }
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ case value::TypeTags::NumberDouble: {
+ if (auto result = representAs<double>(value); result) {
+ return {false, value::TypeTags::NumberDouble, value::bitcastFrom(*result)};
+ }
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ case value::TypeTags::NumberDecimal: {
+ if (auto result = representAs<Decimal128>(value); result) {
+ auto [tag, val] = value::makeCopyDecimal(*result);
+ return {true, tag, val};
+ }
+ return {false, value::TypeTags::Nothing, 0};
+ }
+ default:
+ MONGO_UNREACHABLE
+ }
+}
+
inline TypeTags getWidestNumericalType(TypeTags lhsTag, TypeTags rhsTag) noexcept {
if (lhsTag == TypeTags::NumberDecimal || rhsTag == TypeTags::NumberDecimal) {
return TypeTags::NumberDecimal;
diff --git a/src/mongo/db/exec/sbe/vm/arith.cpp b/src/mongo/db/exec/sbe/vm/arith.cpp
index db2cecf31f5..58cc5b76344 100644
--- a/src/mongo/db/exec/sbe/vm/arith.cpp
+++ b/src/mongo/db/exec/sbe/vm/arith.cpp
@@ -346,6 +346,26 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericMod(value::Type
return {false, value::TypeTags::Nothing, 0};
}
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericNumConvert(
+ value::TypeTags lhsTag, value::Value lhsValue, value::TypeTags targetTag) {
+ if (value::isNumber(lhsTag)) {
+ switch (lhsTag) {
+ case value::TypeTags::NumberInt32:
+ return numericConvLossless<int32_t>(value::bitcastTo<int32_t>(lhsValue), targetTag);
+ case value::TypeTags::NumberInt64:
+ return numericConvLossless<int64_t>(value::bitcastTo<int64_t>(lhsValue), targetTag);
+ case value::TypeTags::NumberDouble:
+ return numericConvLossless<double>(value::bitcastTo<double>(lhsValue), targetTag);
+ case value::TypeTags::NumberDecimal:
+ return numericConvLossless<Decimal128>(value::bitcastTo<Decimal128>(lhsValue),
+ targetTag);
+ default:
+ MONGO_UNREACHABLE
+ }
+ }
+ return {false, value::TypeTags::Nothing, 0};
+}
+
std::tuple<bool, value::TypeTags, value::Value> ByteCode::genericAbs(value::TypeTags operandTag,
value::Value operandValue) {
switch (operandTag) {
diff --git a/src/mongo/db/exec/sbe/vm/vm.cpp b/src/mongo/db/exec/sbe/vm/vm.cpp
index 0d53d526212..33b64f6a14d 100644
--- a/src/mongo/db/exec/sbe/vm/vm.cpp
+++ b/src/mongo/db/exec/sbe/vm/vm.cpp
@@ -35,6 +35,7 @@
#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"
#include "mongo/db/storage/key_string.h"
#include "mongo/util/fail_point.h"
#include "mongo/util/summation.h"
@@ -64,6 +65,7 @@ int Instruction::stackOffset[Instruction::Tags::lastInstruction] = {
-1, // idiv
-1, // mod
0, // negate
+ 0, // numConvert
0, // logicNot
@@ -203,6 +205,17 @@ void CodeFragment::appendAdd() {
appendSimpleInstruction(Instruction::add);
}
+void CodeFragment::appendNumericConvert(value::TypeTags targetTag) {
+ Instruction i;
+ i.tag = Instruction::numConvert;
+ adjustStackSimple(i);
+
+ auto offset = allocateSpace(sizeof(Instruction) + sizeof(targetTag));
+
+ offset += value::writeToMemory(offset, i);
+ offset += value::writeToMemory(offset, targetTag);
+}
+
void CodeFragment::appendSub() {
appendSimpleInstruction(Instruction::sub);
}
@@ -776,7 +789,7 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinRegexMatch(uint
auto stringView = value::getStringView(typeTagInputStr, valueInputStr);
pcrecpp::StringPiece pcreStringView{stringView.data(), static_cast<int>(stringView.size())};
- auto pcreRegex = value::getPrceRegexView(valuePcreRegex);
+ auto pcreRegex = value::getPcreRegexView(valuePcreRegex);
auto regexMatchResult = pcreRegex->PartialMatch(pcreStringView);
return {false, value::TypeTags::Boolean, regexMatchResult};
@@ -872,9 +885,135 @@ std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDoubleDoubleSum
return {false, value::TypeTags::Nothing, 0};
}
+/**
+ * A helper for the bultinDate method. The formal parameters yearOrWeekYear and monthOrWeek carry
+ * values depending on wether the date is a year-month-day or ISOWeekYear.
+ */
+using DateFn = std::function<Date_t(
+ TimeZone, long long, long long, long long, long long, long long, long long, long long)>;
+std::tuple<bool, value::TypeTags, value::Value> builtinDateHelper(
+ DateFn computeDateFn,
+ std::tuple<bool, value::TypeTags, value::Value> tzdb,
+ std::tuple<bool, value::TypeTags, value::Value> yearOrWeekYear,
+ std::tuple<bool, value::TypeTags, value::Value> monthOrWeek,
+ std::tuple<bool, value::TypeTags, value::Value> day,
+ std::tuple<bool, value::TypeTags, value::Value> hour,
+ std::tuple<bool, value::TypeTags, value::Value> minute,
+ std::tuple<bool, value::TypeTags, value::Value> second,
+ std::tuple<bool, value::TypeTags, value::Value> millisecond,
+ std::tuple<bool, value::TypeTags, value::Value> timezone) {
+
+ auto [ownedTzdb, typeTagTzdb, valueTzdb] = tzdb;
+ auto [ownedYearOrWeekYear, typeTagYearOrWeekYear, valueYearOrWeekYear] = yearOrWeekYear;
+ auto [ownedMonthOrWeek, typeTagMonthOrWeek, valueMonthOrWeek] = monthOrWeek;
+ auto [ownedDay, typeTagDay, valueDay] = day;
+ auto [ownedHr, typeTagHr, valueHr] = hour;
+ auto [ownedMin, typeTagMin, valueMin] = minute;
+ auto [ownedSec, typeTagSec, valueSec] = second;
+ auto [ownedMillis, typeTagMillis, valueMillis] = millisecond;
+ auto [ownedTz, typeTagTz, valueTz] = timezone;
+
+ if (typeTagTzdb != value::TypeTags::timeZoneDB || !value::isNumber(typeTagYearOrWeekYear) ||
+ !value::isNumber(typeTagMonthOrWeek) || !value::isNumber(typeTagDay) ||
+ !value::isNumber(typeTagHr) || !value::isNumber(typeTagMin) ||
+ !value::isNumber(typeTagSec) || !value::isNumber(typeTagMillis) ||
+ !value::isString(typeTagTz)) {
+ return {false, value::TypeTags::Nothing, 0};
+ }
+
+ auto timeZoneDB = value::getTimeZoneDBView(valueTzdb);
+ invariant(timeZoneDB);
+
+ auto tzString = value::getStringView(typeTagTz, valueTz);
+ const auto tz = tzString == ""
+ ? timeZoneDB->utcZone()
+ : timeZoneDB->getTimeZone(StringData{tzString.data(), tzString.size()});
+
+ auto date =
+ computeDateFn(tz,
+ value::numericCast<int64_t>(typeTagYearOrWeekYear, valueYearOrWeekYear),
+ value::numericCast<int64_t>(typeTagMonthOrWeek, valueMonthOrWeek),
+ value::numericCast<int64_t>(typeTagDay, valueDay),
+ value::numericCast<int64_t>(typeTagHr, valueHr),
+ value::numericCast<int64_t>(typeTagMin, valueMin),
+ value::numericCast<int64_t>(typeTagSec, valueSec),
+ value::numericCast<int64_t>(typeTagMillis, valueMillis));
+ return {false, value::TypeTags::Date, date.asInt64()};
+}
+
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDate(uint8_t arity) {
+ auto timeZoneDBTuple = getFromStack(0);
+ auto yearTuple = getFromStack(1);
+ auto monthTuple = getFromStack(2);
+ auto dayTuple = getFromStack(3);
+ auto hourTuple = getFromStack(4);
+ auto minuteTuple = getFromStack(5);
+ auto secondTuple = getFromStack(6);
+ auto millisTuple = getFromStack(7);
+ auto timezoneTuple = getFromStack(8);
+
+ return builtinDateHelper(
+ [](TimeZone tz,
+ long long year,
+ long long month,
+ long long day,
+ long long hour,
+ long long min,
+ long long sec,
+ long long millis) -> Date_t {
+ return tz.createFromDateParts(year, month, day, hour, min, sec, millis);
+ },
+ timeZoneDBTuple,
+ yearTuple,
+ monthTuple,
+ dayTuple,
+ hourTuple,
+ minuteTuple,
+ secondTuple,
+ millisTuple,
+ timezoneTuple);
+}
+
+std::tuple<bool, value::TypeTags, value::Value> ByteCode::builtinDateWeekYear(uint8_t arity) {
+ auto timeZoneDBTuple = getFromStack(0);
+ auto yearTuple = getFromStack(1);
+ auto weekTuple = getFromStack(2);
+ auto dayTuple = getFromStack(3);
+ auto hourTuple = getFromStack(4);
+ auto minuteTuple = getFromStack(5);
+ auto secondTuple = getFromStack(6);
+ auto millisTuple = getFromStack(7);
+ auto timezoneTuple = getFromStack(8);
+
+ return builtinDateHelper(
+ [](TimeZone tz,
+ long long year,
+ long long month,
+ long long day,
+ long long hour,
+ long long min,
+ long long sec,
+ long long millis) -> Date_t {
+ return tz.createFromIso8601DateParts(year, month, day, hour, min, sec, millis);
+ },
+ timeZoneDBTuple,
+ yearTuple,
+ weekTuple,
+ dayTuple,
+ hourTuple,
+ minuteTuple,
+ secondTuple,
+ millisTuple,
+ timezoneTuple);
+}
+
std::tuple<bool, value::TypeTags, value::Value> ByteCode::dispatchBuiltin(Builtin f,
uint8_t arity) {
switch (f) {
+ case Builtin::dateParts:
+ return builtinDate(arity);
+ case Builtin::datePartsWeekYear:
+ return builtinDateWeekYear(arity);
case Builtin::split:
return builtinSplit(arity);
case Builtin::regexMatch:
@@ -1093,6 +1232,22 @@ std::tuple<uint8_t, value::TypeTags, value::Value> ByteCode::run(CodeFragment* c
break;
}
+ case Instruction::numConvert: {
+ auto tag = value::readFromMemory<value::TypeTags>(pcPointer);
+ pcPointer += sizeof(tag);
+
+ auto [owned, lhsTag, lhsVal] = getFromStack(0);
+
+ auto [rhsOwned, rhsTag, rhsVal] = genericNumConvert(lhsTag, lhsVal, tag);
+
+ topStack(rhsOwned, rhsTag, rhsVal);
+
+ if (owned) {
+ value::releaseValue(lhsTag, lhsVal);
+ }
+
+ break;
+ }
case Instruction::logicNot: {
auto [owned, tag, val] = getFromStack(0);
diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h
index 635fcff1bbc..12d054bd8ca 100644
--- a/src/mongo/db/exec/sbe/vm/vm.h
+++ b/src/mongo/db/exec/sbe/vm/vm.h
@@ -111,6 +111,7 @@ struct Instruction {
idiv,
mod,
negate,
+ numConvert,
logicNot,
@@ -162,6 +163,8 @@ static_assert(sizeof(Instruction) == sizeof(uint8_t));
enum class Builtin : uint8_t {
split,
regexMatch,
+ dateParts,
+ datePartsWeekYear,
dropFields,
newObj,
ksToString, // KeyString to string
@@ -245,6 +248,7 @@ public:
void appendFail() {
appendSimpleInstruction(Instruction::fail);
}
+ void appendNumericConvert(value::TypeTags targetTag);
private:
void appendSimpleInstruction(Instruction::Tags tag);
@@ -316,6 +320,9 @@ private:
value::Value operandValue);
std::tuple<bool, value::TypeTags, value::Value> genericNot(value::TypeTags tag,
value::Value value);
+ std::tuple<bool, value::TypeTags, value::Value> genericNumConvert(value::TypeTags lhsTag,
+ value::Value lhsValue,
+ value::TypeTags rhsTag);
template <typename Op>
std::pair<value::TypeTags, value::Value> genericCompare(value::TypeTags lhsTag,
value::Value lhsValue,
@@ -371,6 +378,8 @@ private:
value::Value fieldValue);
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> 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);