diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3752331..2b9f4ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ file(GLOB SOURCES "ogawayama/stub/*.cpp" "tateyama/utils/*.cpp" "jogasaki/serializer/*.cpp" + "jogasaki/utils/*.cpp" ) set_source_files_properties( diff --git a/src/jogasaki/utils/decimal.cpp b/src/jogasaki/utils/decimal.cpp new file mode 100644 index 0000000..a161766 --- /dev/null +++ b/src/jogasaki/utils/decimal.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2018-2023 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "decimal.h" + +namespace jogasaki::utils { + +constexpr static std::size_t npos = static_cast(-1); + +std::pair most_significant_non_zero_offset(std::uint64_t v, std::uint64_t zero) { + for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { + std::uint64_t octet = (v >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU; + if (octet != zero) { + if ((octet & 0x80U) != 0) { + return {offset, true}; + } + return {offset, false}; + } + } + return {npos, false}; +} + +std::tuple make_signed_coefficient_full(takatori::decimal::triple value) { + std::uint64_t c_hi = value.coefficient_high(); + std::uint64_t c_lo = value.coefficient_low(); + + if (value.sign() >= 0) { + if(auto [offset, expanded] = most_significant_non_zero_offset(c_hi, 0); offset != npos) { + std::size_t size { sizeof(std::uint64_t) * 2 - offset }; + if(expanded) { + ++size; + } + return { c_hi, c_lo, size }; + } + if(auto [offset, expanded] = most_significant_non_zero_offset(c_lo, 0); offset != npos) { + std::size_t size { sizeof(std::uint64_t) - offset }; + if(expanded) { + ++size; + } + return { c_hi, c_lo, size }; + } + // all values zero + return { c_hi, c_lo, 1 }; + } + + // for negative numbers + + if (value.sign() < 0) { + c_lo = ~c_lo + 1; + c_hi = ~c_hi; + if (c_lo == 0) { + c_hi += 1; // carry up + } + } + + if(auto [offset, expanded] = most_significant_non_zero_offset(c_hi, 0xffU); offset != npos) { + std::size_t size { sizeof(std::uint64_t) * 2 - offset }; + if(expanded) { + ++size; + } + return { c_hi, c_lo, size }; + } + if(auto [offset, expanded] = most_significant_non_zero_offset(c_lo, 0xffU); offset != npos) { + std::size_t size { sizeof(std::uint64_t) - offset }; + if(expanded) { + ++size; + } + return { c_hi, c_lo, size }; + } + return { c_hi, c_lo, 1 }; +} + +constexpr std::size_t max_decimal_coefficient_size = sizeof(std::uint64_t) * 2 + 1; + +bool validate_decimal_coefficient( + std::string_view buf +) { + if(buf.size() < max_decimal_coefficient_size) { + return true; + } + auto first = static_cast(buf[0]); + // positive is OK because coefficient is [0, 2^128) + if (first == 0) { + return true; + } + + if (first == 0xffU) { + // check negative value to avoid -2^128 (0xff 0x00.. 0x00) + auto const* found = std::find_if( + buf.begin() + 1, + buf.end(), + [](auto c) { return c != '\0'; }); + if (found != buf.end()) { + return true; + } + } + return false; +} + +takatori::decimal::triple read_decimal(std::string_view data, std::size_t scale) { + // extract lower 8-octets of coefficient + std::uint64_t c_lo{}; + std::uint64_t shift{}; + for (std::size_t offset = 0; + offset < data.size() && offset < sizeof(std::uint64_t); + ++offset) { + auto pos = data.size() - offset - 1; + std::uint64_t octet { static_cast(data[pos]) }; + c_lo |= octet << shift; + shift += 8; + } + + // extract upper 8-octets of coefficient + std::uint64_t c_hi {}; + shift = 0; + for ( + std::size_t offset = sizeof(std::uint64_t); + offset < data.size() && offset < sizeof(std::uint64_t) * 2; + ++offset) { + auto pos = data.size() - offset - 1; + std::uint64_t octet { static_cast(data[pos]) }; + c_hi |= octet << shift; + shift += 8; + } + + bool negative = (static_cast(data[0]) & 0x80U) != 0; + + if (negative) { + // sign extension + if (data.size() < sizeof(std::uint64_t) * 2) { + auto mask = std::numeric_limits::max(); // 0xfff..... + if(data.size() < sizeof(std::uint64_t)) { + std::size_t rest = data.size() * 8U; + c_lo |= mask << rest; + c_hi = mask; + } else { + std::size_t rest = (data.size() - sizeof(std::uint64_t)) * 8U; + c_hi |= mask << rest; + } + } + + c_lo = ~c_lo + 1; + c_hi = ~c_hi; + if (c_lo == 0) { + c_hi += 1; // carry up + } + } + + return takatori::decimal::triple{ + negative ? -1 : +1, + c_hi, + c_lo, + -static_cast(scale), + }; +} + +void create_decimal( + std::int8_t sign, + std::uint64_t lo, + std::uint64_t hi, + std::size_t sz, + decimal_buffer& out +) { + auto base_ = out.data(); + std::size_t pos_ = 0; + + if (sz > sizeof(std::uint64_t) * 2) { + // write sign bit + *(base_ + pos_) = sign >= 0 ? '\x00' : '\xFF'; // NOLINT + ++pos_; + --sz; + } + + for (std::size_t offset = 0, n = std::min(sz, sizeof(std::uint64_t)); offset < n; ++offset) { + *(base_ + pos_ + sz - offset - 1) = static_cast(lo >> (offset * 8U)); //NOLINT + } + if (sz > sizeof(std::uint64_t)) { + for (std::size_t offset = 0, n = std::min(sz - sizeof(std::uint64_t), sizeof(std::uint64_t)); offset < n; ++offset) { + *(base_ + pos_ + sz - offset - sizeof(std::uint64_t) - 1) = static_cast(hi >> (offset * 8U)); //NOLINT + } + } +} + +} + diff --git a/src/jogasaki/utils/decimal.h b/src/jogasaki/utils/decimal.h new file mode 100644 index 0000000..d604eb5 --- /dev/null +++ b/src/jogasaki/utils/decimal.h @@ -0,0 +1,72 @@ +/* + * Copyright 2018-2023 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include + +namespace jogasaki::utils { + +/** + * @brief create signed coefficient from triple that has unsigned components + * @param value source triple + * @return `hi`, `lo` and `sz` return values: + * hi - more significant 64-bit + * lo - less significant 64 bit + * sz - min size in bytes to represent the signed coefficient. The `sz` bytes from the least significant byte in + * result 128-bit (concatenated `hi` with `lo`) represents the result. For valid input, `sz` ranges from 1 to 17. + * The `sz` being 17 is the special case where most significant byte (not part of `hi` or `lo`) is 0x00 or 0xFF + * to represent only sign. + */ +std::tuple make_signed_coefficient_full(takatori::decimal::triple value); + +/** + * @brief validate the decimal data in the buffer + * @param buf the data to validate + * @return true if buffer has valid decimal coefficient + * @return false otherwise + */ +bool validate_decimal_coefficient(std::string_view buf); + +/** + * @brief read decimal from the buffer and return triple + * @param data the decimal data to read + * @param scale the scale of the result decimal + * @return the triple to represent the decimal data + */ +takatori::decimal::triple read_decimal(std::string_view data, std::size_t scale); + +constexpr std::size_t max_decimal_length = sizeof(std::uint64_t) * 2 + 1; + +using decimal_buffer = std::array; + +/** + * @brief write decimal to the buffer + * @param triple the decimal triple to write to the output buffer + * @param out the output buffer + */ +void create_decimal( + std::int8_t sign, + std::uint64_t lo, + std::uint64_t hi, + std::size_t sz, + decimal_buffer& out +); + +} diff --git a/src/ogawayama/stub/transaction.cpp b/src/ogawayama/stub/transaction.cpp index a3bb922..7e6c421 100644 --- a/src/ogawayama/stub/transaction.cpp +++ b/src/ogawayama/stub/transaction.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "jogasaki/utils/decimal.h" #include "prepared_statementImpl.h" #include "transactionImpl.h" @@ -112,86 +113,21 @@ class parameter { v->set_nano_adjustment(data.first.subsecond().count()); return parameter_; } + ::jogasaki::proto::sql::request::Parameter operator()(const decimal_type& triple) { auto* value = ¶meter_; - auto [c_hi, c_lo, c_size] = make_signed_coefficient_full(triple); + auto [hi, lo, sz] = jogasaki::utils::make_signed_coefficient_full(triple); + jogasaki::utils::decimal_buffer out{}; + jogasaki::utils::create_decimal(triple.sign(), lo, hi, sz, out); - // the following part is taken from https://github.com/project-tsurugi/jogasaki/blob/206d07d700adc25d19c031bc1df9a08aee16555c/src/jogasaki/api/kvsservice/serializer.cpp#L219-L245 with using make_signed_coefficient_full() - { - std::string buf{}; - const auto buflen = sizeof(c_lo) + sizeof(c_hi); - buf.reserve(buflen); - auto v = c_lo; - for (int i = 0; i < 8; i++) { - buf[15 - i] = static_cast(v & 0xffU); - v >>= 8U; - } - v = c_hi; - for (int i = 0; i < 8; i++) { - buf[7 - i] = static_cast(v & 0xffU); - v >>= 8U; - } - // skip zero-padding - std::size_t start = 0; - while (start < buflen - 1) { - if (buf[start] != 0) { - break; - } - start++; - } - auto *decimal = value->mutable_decimal_value(); - // NOTE: buf.size() returns 0, not 16 - decimal->set_unscaled_value(std::addressof(buf[start]), buflen - start); - decimal->set_exponent(triple.exponent()); - } + auto *decimal = value->mutable_decimal_value(); + decimal->set_unscaled_value(out.data(), sz); + decimal->set_exponent(triple.exponent()); return parameter_; } private: ::jogasaki::proto::sql::request::Parameter parameter_{}; - - // the following method is the copy of https://github.com/project-tsurugi/jogasaki/blob/2a404cdaf6e5599d1b2b8742d3568de58b3ee670/src/jogasaki/serializer/value_output.cpp#L165-L205 - std::tuple make_signed_coefficient_full(takatori::decimal::triple value) { - std::uint64_t c_hi = value.coefficient_high(); - std::uint64_t c_lo = value.coefficient_low(); - - if (value.sign() >= 0) { - for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { - std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU; - if (octet != 0) { - std::size_t size { sizeof(std::uint64_t) * 2 - offset }; - if ((octet & 0x80U) != 0) { - ++size; - } - return { c_hi, c_lo, size }; - } - } - return { c_hi, c_lo, sizeof(std::uint64_t) + 1 }; - } - - // for negative numbers - - if (value.sign() < 0) { - c_lo = ~c_lo + 1; - c_hi = ~c_hi; - if (c_lo == 0) { - c_hi += 1; // carry up - } - } - - for (std::size_t offset = 0; offset < sizeof(std::uint64_t); ++offset) { - std::uint64_t octet = (c_hi >> ((sizeof(std::uint64_t) - offset - 1U) * 8U)) & 0xffU; - if (octet != 0xffU) { - std::size_t size { sizeof(std::uint64_t) * 2 - offset }; - if ((octet & 0x80U) == 0) { - ++size; - } - return { c_hi, c_lo, size }; - } - } - return { c_hi, c_lo, sizeof(std::uint64_t) + 1 }; - } - }; /**