diff --git a/example/Jamfile b/example/Jamfile index 96422adfa..854d73423 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -71,7 +71,7 @@ run_example deletes : 2_simple/deletes.cpp run_example callbacks : 2_simple/callbacks.cpp : $(regular_args) ; run_example coroutines_cpp11 : 2_simple/coroutines_cpp11.cpp /boost/mysql/test//boost_context_lib - : $(regular_args) ; + : $(regular_args) : : off ; # TODO: remove when Boost.Context removes warnings run_example batch_inserts : 2_simple/batch_inserts.cpp /boost/mysql/test//boost_json_lib : $(hostname) : run_batch_inserts.py ; run_example batch_inserts_generic : 2_simple/batch_inserts_generic.cpp /boost/mysql/test//boost_json_lib @@ -119,4 +119,5 @@ run_example connection_pool : # Uses heavily Boost.Context coroutines, which aren't fully supported by asan norecover:no enable:no + off # TODO: remove when Boost.Context removes warnings ; diff --git a/include/boost/mysql.hpp b/include/boost/mysql.hpp index 149900a11..69ac6cda1 100644 --- a/include/boost/mysql.hpp +++ b/include/boost/mysql.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/mysql/decimal.hpp b/include/boost/mysql/decimal.hpp new file mode 100644 index 000000000..ab8951b3e --- /dev/null +++ b/include/boost/mysql/decimal.hpp @@ -0,0 +1,17 @@ +// +// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_DECIMAL_HPP +#define BOOST_MYSQL_DECIMAL_HPP + +// Provides the required specializations to be able to use Boost.Decimal types +// with the static interface and with format_sql. +// This header doesn't contain anything visible because none of the included +// declarations should be used directly. +#include + +#endif diff --git a/include/boost/mysql/detail/typing/decimal.hpp b/include/boost/mysql/detail/typing/decimal.hpp new file mode 100644 index 000000000..b466401ec --- /dev/null +++ b/include/boost/mysql/detail/typing/decimal.hpp @@ -0,0 +1,169 @@ +// +// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_DETAIL_TYPING_DECIMAL_HPP +#define BOOST_MYSQL_DETAIL_TYPING_DECIMAL_HPP + +#include + +#ifdef BOOST_MYSQL_CXX14 + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace boost { +namespace mysql { +namespace detail { + +// Get the number of decimal digits required to represent the given column. +// The server gives this information in required displayed characters, +// but there's a one-to-one mapping to precision. +// This same algorithm is employed by the server. +// Returns -1 in case of error +inline int decimal_required_precision(const metadata& meta) +{ + constexpr unsigned max_precision = 65u; // Max value allowed by the server + unsigned radix_chars = meta.decimals() > 0u ? 1u : 0u; // Number of characters used for the decimal point + unsigned sign_chars = meta.is_unsigned() ? 0u : 1u; // Number of characters used for the sign + unsigned res = meta.column_length() - radix_chars - sign_chars; + return res > max_precision ? -1 : static_cast(res); +} + +// meta_check implementation for decimal types +inline bool meta_check_decimal_impl(meta_check_context& ctx, int cpp_precision, const char* cpp_type_name) +{ + // Check the number of decimals + int required_precision = decimal_required_precision(ctx.current_meta()); + if (required_precision == -1) + { + ctx.add_error() << "Invalid precision received from the server for decimal column: '" + << column_type_to_str(ctx.current_meta()) << "'"; // TODO: better msg + } + if (required_precision > cpp_precision) + { + auto& stream = ctx.add_error(); + stream << "Incompatible types for field "; + ctx.insert_field_name(stream); + stream << ": C++ type '" << cpp_type_name << "' has a precision of " << cpp_precision + << " decimals, while the DB type requires a precision of " << required_precision + << " decimals"; + } + + // Check type (encoded as this function's return value) + return ctx.current_meta().type() == column_type::decimal; +} + +// type names for decimals +constexpr const char* decimal_type_name(decimal::decimal32) { return "decimal32"; } +constexpr const char* decimal_type_name(decimal::decimal64) { return "decimal64"; } +constexpr const char* decimal_type_name(decimal::decimal128) { return "decimal128"; } +constexpr const char* decimal_type_name(decimal::decimal32_fast) { return "decimal32_fast"; } +constexpr const char* decimal_type_name(decimal::decimal64_fast) { return "decimal64_fast"; } +constexpr const char* decimal_type_name(decimal::decimal128_fast) { return "decimal128_fast"; } + +// precisions for decimals +constexpr int decimal_precision(decimal::decimal32) { return 7; } +constexpr int decimal_precision(decimal::decimal64) { return 16; } +constexpr int decimal_precision(decimal::decimal128) { return 34; } +constexpr int decimal_precision(decimal::decimal32_fast) { return 7; } +constexpr int decimal_precision(decimal::decimal64_fast) { return 16; } +constexpr int decimal_precision(decimal::decimal128_fast) { return 34; } + +template +struct decimal_readable_field_traits +{ + static constexpr bool is_supported = true; + static BOOST_INLINE_CONSTEXPR const char* type_name = decimal_type_name(Decimal()); + static bool meta_check(meta_check_context& ctx) + { + return meta_check_decimal_impl(ctx, decimal_precision(Decimal()), type_name); + } + static error_code parse(field_view input, Decimal& output) + { + // Check type + if (!input.is_string()) + return client_errc::static_row_parsing_error; + auto str = input.get_string(); + + // Invoke decimal's charconv. MySQL always uses the fixed format. + auto res = decimal::from_chars(str.begin(), str.end(), output, decimal::chars_format::fixed); + if (res.ec != std::errc{} || res.ptr != str.end()) + return client_errc::static_row_parsing_error; + + // Done + return error_code(); + } +}; + +// clang-format off +template <> struct readable_field_traits : decimal_readable_field_traits {}; +template <> struct readable_field_traits : decimal_readable_field_traits {}; +template <> struct readable_field_traits : decimal_readable_field_traits {}; +template <> struct readable_field_traits : decimal_readable_field_traits {}; +template <> struct readable_field_traits : decimal_readable_field_traits {}; +template <> struct readable_field_traits : decimal_readable_field_traits {}; +// clang-format on + +// format_sql support +template +struct decimal_formatter +{ + const char* parse(const char* begin, const char*) { return begin; } + void format(Decimal value, format_context_base& ctx) const + { + // MySQL's DECIMAL doesn't support NaN or Inf + if (decimal::isnan(value) || decimal::isinf(value)) + { + ctx.add_error(client_errc::unformattable_value); + return; + } + + // MySQL's DECIMAL uses fixed precision and a max precision of 65. + // With sign and radix, that's 67 characters max. + // Boost.Decimal can represent values that might yield longer representations + // (as it uses floating point representations). Buffer overflows cause a value_too_large error + char buffer[67]{}; + auto result = decimal::to_chars(buffer, buffer + sizeof(buffer), value, decimal::chars_format::fixed); + if (result.ec != std::errc()) + { + ctx.add_error( + result.ec == std::errc::value_too_large ? error_code(client_errc::unformattable_value) + : error_code(std::make_error_code(result.ec)) + ); + return; + } + ctx.append_raw(runtime(string_view(buffer, result.ptr - buffer))); + } +}; + +} // namespace detail + +// clang-format off +template <> struct formatter : detail::decimal_formatter {}; +template <> struct formatter : detail::decimal_formatter {}; +template <> struct formatter : detail::decimal_formatter {}; +template <> struct formatter : detail::decimal_formatter {}; +template <> struct formatter : detail::decimal_formatter {}; +template <> struct formatter : detail::decimal_formatter {}; +// clang-format on + +} // namespace mysql +} // namespace boost + +#endif + +#endif diff --git a/include/boost/mysql/detail/typing/meta_check_context.hpp b/include/boost/mysql/detail/typing/meta_check_context.hpp index c9367aa36..1e5c35bfa 100644 --- a/include/boost/mysql/detail/typing/meta_check_context.hpp +++ b/include/boost/mysql/detail/typing/meta_check_context.hpp @@ -68,23 +68,6 @@ class meta_check_context metadata_collection_view meta_{}; bool nullability_checked_{}; - std::ostringstream& add_error() - { - if (!errors_) - errors_.reset(new std::ostringstream); - else - *errors_ << '\n'; - return *errors_; - } - - void insert_field_name(std::ostringstream& os) - { - if (has_field_names(name_table_)) - os << "'" << name_table_[current_index_] << "'"; - else - os << "in position " << current_index_; - } - public: meta_check_context( span pos_map, @@ -111,6 +94,23 @@ class meta_check_context bool nullability_checked() const noexcept { return nullability_checked_; } // Error reporting + std::ostringstream& add_error() + { + if (!errors_) + errors_.reset(new std::ostringstream); + else + *errors_ << '\n'; + return *errors_; + } + + void insert_field_name(std::ostringstream& os) + { + if (has_field_names(name_table_)) + os << "'" << name_table_[current_index_] << "'"; + else + os << "in position " << current_index_; + } + BOOST_MYSQL_DECL void add_field_absent_error(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 30e2b26bb..d1d7941fc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,6 +36,7 @@ target_link_libraries( boost_mysql_compiled Boost::unit_test_framework Boost::pfr + Boost::decimal ) target_include_directories( boost_mysql_testing diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 07d83fc68..5d3c886bf 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable( test/connection_pool.cpp test/db_specific.cpp test/database_types.cpp + test/decimal.cpp # Snippets test/snippets/tutorials.cpp diff --git a/test/integration/db_setup.sql b/test/integration/db_setup.sql index 650aa56db..11851d7c0 100644 --- a/test/integration/db_setup.sql +++ b/test/integration/db_setup.sql @@ -466,6 +466,22 @@ INSERT INTO types_binary VALUES ("empty", "", "", "", "", "", "") ; +CREATE TABLE types_decimal( + id VARCHAR(50) NOT NULL PRIMARY KEY, + field_4 DECIMAL(4), -- no scale + field_7 DECIMAL(7, 7), -- scale == precision, max precision supported by decimal32 + field_10 DECIMAL, -- default precision, should be (10, 0) + field_16 DECIMAL(16, 4), -- max precision supported by decimal64 + field_20 DECIMAL(20, 2) UNSIGNED, -- unsigned + field_34 DECIMAL(34, 30) -- max precision supported by decimal128, max supported scale +); +INSERT INTO types_decimal VALUES + ("regular", 213, 0.1214295, 9000, 20.1234, 121.20, 1234.567890123456789012345678901234), + ("negative", -213, -0.1214295, -9000, -20.1234, NULL, -1234.567890123456789012345678901234), + ("min", -9999, -0.9999999, -9999999999, -999999999999.9999, 0, -9999.999999999999999999999999999999), + ("max", 9999, 0.9999999, 9999999999, 999999999999.9999, 999999999999999999.99, -9999.999999999999999999999999999999) +; + CREATE TABLE types_not_implemented( id VARCHAR(50) NOT NULL PRIMARY KEY, field_decimal DECIMAL, diff --git a/test/integration/include/test_integration/metadata_validator.hpp b/test/integration/include/test_integration/metadata_validator.hpp index 7fdb0794d..770ac1ec1 100644 --- a/test/integration/include/test_integration/metadata_validator.hpp +++ b/test/integration/include/test_integration/metadata_validator.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include namespace boost { @@ -27,7 +29,8 @@ class meta_validator column_type type, std::vector flags = {}, unsigned decimals = 0, - std::vector ignore_flags = {} + std::vector ignore_flags = {}, + boost::optional length = {} ) : table_(std::move(table)), org_table_(table_), @@ -36,7 +39,8 @@ class meta_validator decimals_(decimals), type_(type), flags_(std::move(flags)), - ignore_flags_(std::move(ignore_flags)) + ignore_flags_(std::move(ignore_flags)), + length_(length) { } meta_validator( @@ -71,6 +75,7 @@ class meta_validator column_type type_; std::vector flags_; std::vector ignore_flags_; + boost::optional length_; }; void validate_meta(const metadata_collection_view& actual, const std::vector& expected); diff --git a/test/integration/src/metadata_validator.cpp b/test/integration/src/metadata_validator.cpp index 0641da78e..03a1686e8 100644 --- a/test/integration/src/metadata_validator.cpp +++ b/test/integration/src/metadata_validator.cpp @@ -14,10 +14,7 @@ using namespace boost::mysql::test; -#define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) \ - { \ - #getter, &boost::mysql::metadata::getter \ - } +#define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) {#getter, &boost::mysql::metadata::getter} static struct flag_entry { @@ -78,6 +75,12 @@ void meta_validator::validate(const boost::mysql::metadata& value) const BOOST_TEST(!(value.*entry.getter)(), entry.name); } } + + // Column length + if (length_.has_value()) + { + BOOST_TEST(length_.value() == value.column_length()); + } } void boost::mysql::test::validate_meta( diff --git a/test/integration/test/decimal.cpp b/test/integration/test/decimal.cpp new file mode 100644 index 000000000..1a7af2eb8 --- /dev/null +++ b/test/integration/test/decimal.cpp @@ -0,0 +1,97 @@ +// +// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#ifdef BOOST_MYSQL_CXX14 + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "test_common/network_result.hpp" +#include "test_integration/any_connection_fixture.hpp" +#include "test_integration/metadata_validator.hpp" + +using namespace boost::mysql; +using namespace boost::mysql::test; +using boost::optional; +using boost::test_tools::per_element; +using boost::describe::operators::operator==; +using boost::describe::operators::operator<<; +namespace decimal = boost::decimal; + +// For now, we don't support decimals as statement parameters +// (only when reading rows) +struct decimal_row +{ + std::string id; + optional field_4; + optional field_7; + optional field_10; + optional field_16; + optional field_20; + optional field_34; +}; +BOOST_DESCRIBE_STRUCT(decimal_row, (), (id, field_4, field_7, field_10, field_16, field_20, field_34)) + +namespace { + +BOOST_AUTO_TEST_SUITE(test_decimal) + +BOOST_FIXTURE_TEST_CASE(select, any_connection_fixture) +{ + using namespace boost::decimal; + + // Setup + connect(); + + // Issue the query + static_results result; + conn.async_execute("SELECT * FROM types_decimal ORDER BY id", result, as_netresult).validate_no_error(); + + // Validate metadata + std::vector id_flags{ + &metadata::is_primary_key, + &metadata::is_not_null, + &metadata::has_no_default_value, + }; + std::vector expected_meta{ + {"types_decimal", "id", column_type::varchar, std::move(id_flags), 0u, {}, {} }, + {"types_decimal", "field_4", column_type::decimal, {}, 0u, {}, 5u }, + {"types_decimal", "field_7", column_type::decimal, {}, 7u, {}, 9u }, + {"types_decimal", "field_10", column_type::decimal, {}, 0u, {}, 11u}, + {"types_decimal", "field_16", column_type::decimal, {}, 4u, {}, 18u}, + {"types_decimal", "field_20", column_type::decimal, {&metadata::is_unsigned}, 2u, {}, 21u}, + {"types_decimal", "field_34", column_type::decimal, {}, 30u, {}, 36u}, + }; + validate_meta(result.meta(), expected_meta); + + // Validate rows + // clang-format off + const decimal_row expected_rows [] = { + {"max", 9999_df, 0.9999999_dff, 9999999999_dd, 999999999999.9999_ddf, 999999999999999999.99_dl, -9999.999999999999999999999999999999_dlf}, + {"min", -9999_df, -0.9999999_dff, -9999999999_dd, -999999999999.9999_ddf, 0_dl, -9999.999999999999999999999999999999_dlf}, + {"negative", -213_df, -0.1214295_dff, -9000_dd, -20.1234_ddf, {}, -1234.567890123456789012345678901234_dlf}, + {"regular", 213_df, 0.1214295_dff, 9000_dd, 20.1234_ddf, 121.20_dl, 1234.567890123456789012345678901234_dlf}, + }; + // clang-format on + BOOST_TEST(result.rows() == expected_rows, per_element()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace + +#endif diff --git a/test/unit/test/detail/typing/pos_map.cpp b/test/unit/test/detail/typing/pos_map.cpp index 45bfdcaae..9b133209d 100644 --- a/test/unit/test/detail/typing/pos_map.cpp +++ b/test/unit/test/detail/typing/pos_map.cpp @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(reset_nonempty) BOOST_TEST(map[0] == pos_absent); BOOST_TEST(map[1] == pos_absent); BOOST_TEST(map[2] == pos_absent); - BOOST_TEST(map[3] == 45u); // didn't modify any extra storage + BOOST_TEST(storage[3] == 45u); // didn't modify any extra storage } BOOST_AUTO_TEST_CASE(add_field_empty) diff --git a/test/unit/test/format_sql/formattable.cpp b/test/unit/test/format_sql/formattable.cpp index 09b81f503..fa1d4ce1b 100644 --- a/test/unit/test/format_sql/formattable.cpp +++ b/test/unit/test/format_sql/formattable.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -179,6 +180,16 @@ using format_fn_t = void (*)(int, format_context_base&); using format_seq_t = format_sequence, format_fn_t>; BOOST_MYSQL_CHECK_FORMATTABLE(format_seq_t, true) +// decimals are formattable +#ifdef BOOST_MYSQL_CXX14 +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal32, true); +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal32_fast, true); +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal64, true); +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal64_fast, true); +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal128, true); +BOOST_MYSQL_CHECK_FORMATTABLE(boost::decimal::decimal128_fast, true); +#endif + // other stuff not accepted BOOST_MYSQL_CHECK_FORMATTABLE(void*, false) BOOST_MYSQL_CHECK_FORMATTABLE(field*, false) diff --git a/test/unit/test/format_sql/individual_value.cpp b/test/unit/test/format_sql/individual_value.cpp index 84d23cb73..1d9168179 100644 --- a/test/unit/test/format_sql/individual_value.cpp +++ b/test/unit/test/format_sql/individual_value.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -502,6 +503,74 @@ BOOST_AUTO_TEST_CASE(std_optional) } #endif +#ifdef BOOST_MYSQL_CXX14 +BOOST_AUTO_TEST_CASE(decimal32_) +{ + using namespace boost::decimal; + BOOST_TEST(format_sql(opts, single_fmt, 200_df) == "SELECT 200;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.56789_df) == "SELECT 1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.142099e5_df) == "SELECT 114209.9;"); + BOOST_TEST(format_sql(opts, single_fmt, -1.56789_df) == "SELECT -1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 9999999_df) == "SELECT 9999999;"); + BOOST_TEST(format_sql(opts, single_fmt, -9999999_df) == "SELECT -9999999;"); + BOOST_TEST(format_sql(opts, single_fmt, 0.000001_df) == "SELECT 0.000001;"); + + // Outside the range of DECIMAL(7), but can be used with more precise decimals + // BOOST_TEST(format_sql(opts, single_fmt, 9.999999e15_df) == "SELECT 999999900000000;"); + // BOOST_TEST(format_sql(opts, single_fmt, 1e-15_df) == "SELECT 0.000000000000001;"); +} + +BOOST_AUTO_TEST_CASE(decimal64_) +{ + using namespace boost::decimal; + BOOST_TEST(format_sql(opts, single_fmt, 200_dd) == "SELECT 200;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.56789_dd) == "SELECT 1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.142099e5_dd) == "SELECT 114209.9;"); + BOOST_TEST(format_sql(opts, single_fmt, -1.56789_dd) == "SELECT -1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 9999999999999999_dd) == "SELECT 9999999999999999;"); + BOOST_TEST(format_sql(opts, single_fmt, -9999999999999999_dd) == "SELECT -9999999999999999;"); + BOOST_TEST(format_sql(opts, single_fmt, 99999.99999999999_dd) == "SELECT 99999.99999999999;"); + BOOST_TEST(format_sql(opts, single_fmt, -99999.99999999999_dd) == "SELECT -99999.99999999999;"); +} + +BOOST_AUTO_TEST_CASE(decimal128_) +{ + using namespace boost::decimal; + BOOST_TEST(format_sql(opts, single_fmt, 200_dl) == "SELECT 200;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.56789_dl) == "SELECT 1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.142099e5_dl) == "SELECT 114209.9;"); + BOOST_TEST(format_sql(opts, single_fmt, -1.56789_dl) == "SELECT -1.56789;"); + BOOST_TEST( + format_sql(opts, single_fmt, "9999999999999999999999999999999999"_dl) == + "SELECT 9999999999999999999999999999999999;" + ); + BOOST_TEST( + format_sql(opts, single_fmt, -"9999999999999999999999999999999999"_dl) == + "SELECT -9999999999999999999999999999999999;" + ); + BOOST_TEST( + format_sql(opts, single_fmt, 9.999999999999999999999999999999999_dl) == + "SELECT 9.999999999999999999999999999999999;" + ); + BOOST_TEST( + format_sql(opts, single_fmt, -9.999999999999999999999999999999999_dl) == + "SELECT -9.999999999999999999999999999999999;" + ); +} + +BOOST_AUTO_TEST_CASE(decimal_fast) +{ + // Spotcheck: fast decimals are also formattable + using namespace boost::decimal; + BOOST_TEST(format_sql(opts, single_fmt, 1.56789_dff) == "SELECT 1.56789;"); + BOOST_TEST(format_sql(opts, single_fmt, 1.5678912345678_ddf) == "SELECT 1.5678912345678;"); + BOOST_TEST( + format_sql(opts, single_fmt, 1.567891234567887237237943928290828_dlf) == + "SELECT 1.567891234567887237237943928290828;" + ); +} +#endif + // // Errors when formatting individual fields // @@ -685,4 +754,23 @@ BOOST_AUTO_TEST_CASE(boost_optional_error) { optional_error_test(); } #endif +#ifdef BOOST_MYSQL_CXX14 +BOOST_AUTO_TEST_CASE(decimal_error) +{ + using namespace boost::decimal; + + // NaN and Inf + using lims32 = std::numeric_limits; + BOOST_TEST(format_single_error("{}", lims32::infinity()) == client_errc::unformattable_value); + BOOST_TEST(format_single_error("{}", -lims32::infinity()) == client_errc::unformattable_value); + BOOST_TEST(format_single_error("{}", lims32::quiet_NaN()) == client_errc::unformattable_value); + + // Too long values + BOOST_TEST(format_single_error("{}", 9.99e90_df) == client_errc::unformattable_value); + BOOST_TEST(format_single_error("{}", 1e-94_df) == client_errc::unformattable_value); + // Subnormal values don't seem to work +} + +#endif + BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/ci/ci_util/install_boost.py b/tools/ci/ci_util/install_boost.py index 4401f419a..fea0f6832 100644 --- a/tools/ci/ci_util/install_boost.py +++ b/tools/ci/ci_util/install_boost.py @@ -57,6 +57,9 @@ def install_boost( # Put our library inside boost root _copy_lib_to_boost(source_dir) + # Clone Boost.Decimal (TODO: this is just for the review) + run(['git', 'clone', '-b', 'develop', '--depth', '1', 'https://github.com/cppalliance/decimal.git', 'libs/decimal']) + # Install Boost dependencies submodules = [ 'libs/context', @@ -74,6 +77,7 @@ def install_boost( run(['python', 'tools/boostdep/depinst/depinst.py', '../tools/quickbook']) else: run(["python", "tools/boostdep/depinst/depinst.py", "--include", "example", "mysql"]) + # Bootstrap if IS_WINDOWS: