diff options
author | Eric Cox <eric.cox@mongodb.com> | 2020-07-07 15:18:52 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-07-24 20:19:03 +0000 |
commit | b9515a079627f792c910c8ed2bb683cc9b071ebd (patch) | |
tree | ba4f826742cedb7aa5bda6f3dbd41ae09136261e /src/mongo/db/exec | |
parent | f69521a0ec5a98e91a38077704b3bdca8377ebb6 (diff) | |
download | mongo-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/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/expression.cpp | 50 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/expressions/expression.h | 30 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/sbe_numeric_convert_test.cpp | 402 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value.h | 55 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/arith.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.cpp | 157 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.h | 9 |
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); |