// // 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_READABLE_FIELD_TRAITS_HPP #define BOOST_MYSQL_DETAIL_TYPING_READABLE_FIELD_TRAITS_HPP #include <boost/mysql/client_errc.hpp> #include <boost/mysql/date.hpp> #include <boost/mysql/datetime.hpp> #include <boost/mysql/diagnostics.hpp> #include <boost/mysql/error_code.hpp> #include <boost/mysql/field_kind.hpp> #include <boost/mysql/field_view.hpp> #include <boost/mysql/metadata.hpp> #include <boost/mysql/metadata_collection_view.hpp> #include <boost/mysql/string_view.hpp> #include <boost/mysql/time.hpp> #include <boost/mysql/detail/config.hpp> #include <boost/mysql/detail/typing/meta_check_context.hpp> #include <boost/mysql/detail/typing/pos_map.hpp> #include <boost/mysql/detail/void_t.hpp> #include <boost/mp11/algorithm.hpp> #include <boost/mp11/utility.hpp> #include <cstdint> #include <limits> #include <string> #include <type_traits> namespace boost { namespace mysql { namespace detail { // Helpers for integers template <class SignedInt> error_code parse_signed_int(field_view input, SignedInt& output) { using unsigned_t = typename std::make_unsigned<SignedInt>::type; using limits_t = std::numeric_limits<SignedInt>; auto kind = input.kind(); if (kind == field_kind::int64) { auto v = input.get_int64(); if (v < (limits_t::min)() || v > (limits_t::max)()) { return client_errc::static_row_parsing_error; } output = static_cast<SignedInt>(v); return error_code(); } else if (kind == field_kind::uint64) { auto v = input.get_uint64(); if (v > static_cast<unsigned_t>((limits_t::max)())) { return client_errc::static_row_parsing_error; } output = static_cast<SignedInt>(v); return error_code(); } else { return client_errc::static_row_parsing_error; } } template <class UnsignedInt> error_code parse_unsigned_int(field_view input, UnsignedInt& output) { if (input.kind() != field_kind::uint64) { return client_errc::static_row_parsing_error; } auto v = input.get_uint64(); if (v > (std::numeric_limits<UnsignedInt>::max)()) { return client_errc::static_row_parsing_error; } output = static_cast<UnsignedInt>(v); return error_code(); } // We want all integer types to be allowed as fields. Some integers // may have the same width as others, but different type (e.g. long and long long // may both be 64-bit, but different types). Auxiliar int_traits to allow this to work template <class T, bool is_signed = std::is_signed<T>::value, std::size_t width = sizeof(T)> struct int_traits { static constexpr bool is_supported = false; }; template <class T> struct int_traits<T, true, 1> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int8_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template <class T> struct int_traits<T, false, 1> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint8_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template <class T> struct int_traits<T, true, 2> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int16_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: return true; case column_type::smallint: case column_type::year: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template <class T> struct int_traits<T, false, 2> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint16_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template <class T> struct int_traits<T, true, 4> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int32_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: return true; case column_type::int_: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template <class T> struct int_traits<T, false, 4> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint32_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: return ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); } }; template <class T> struct int_traits<T, true, 8> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "int64_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: return true; case column_type::bigint: return !ctx.current_meta().is_unsigned(); default: return false; } } static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); } }; template <class T> struct int_traits<T, false, 8> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "uint64_t"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::tinyint: case column_type::smallint: case column_type::year: case column_type::mediumint: case column_type::int_: case column_type::bigint: return ctx.current_meta().is_unsigned(); case column_type::bit: return true; default: return false; } } static error_code parse(field_view input, std::uint64_t& output) { return parse_unsigned_int(input, output); } }; // Traits template <typename T, class EnableIf = void> struct readable_field_traits { static constexpr bool is_supported = false; }; template <> struct readable_field_traits<char, void> : int_traits<char> { }; template <> struct readable_field_traits<signed char, void> : int_traits<signed char> { }; template <> struct readable_field_traits<unsigned char, void> : int_traits<unsigned char> { }; template <> struct readable_field_traits<short, void> : int_traits<short> { }; template <> struct readable_field_traits<unsigned short, void> : int_traits<unsigned short> { }; template <> struct readable_field_traits<int, void> : int_traits<int> { }; template <> struct readable_field_traits<unsigned int, void> : int_traits<unsigned int> { }; template <> struct readable_field_traits<long, void> : int_traits<long> { }; template <> struct readable_field_traits<unsigned long, void> : int_traits<unsigned long> { }; template <> struct readable_field_traits<long long, void> : int_traits<long long> { }; template <> struct readable_field_traits<unsigned long long, void> : int_traits<unsigned long long> { }; template <> struct readable_field_traits<bool, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "bool"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::tinyint && !ctx.current_meta().is_unsigned(); } static error_code parse(field_view input, bool& output) { if (input.kind() != field_kind::int64) { return client_errc::static_row_parsing_error; } output = input.get_int64() != 0; return error_code(); } }; template <> struct readable_field_traits<float, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "float"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::float_; } static error_code parse(field_view input, float& output) { if (input.kind() != field_kind::float_) { return client_errc::static_row_parsing_error; } output = input.get_float(); return error_code(); } }; template <> struct readable_field_traits<double, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "double"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::float_: case column_type::double_: return true; default: return false; } } static error_code parse(field_view input, double& output) { auto kind = input.kind(); if (kind == field_kind::float_) { output = input.get_float(); return error_code(); } else if (kind == field_kind::double_) { output = input.get_double(); return error_code(); } else { return client_errc::static_row_parsing_error; } } }; template <class Allocator> struct readable_field_traits<std::basic_string<char, std::char_traits<char>, Allocator>, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "string"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::decimal: case column_type::char_: case column_type::varchar: case column_type::text: case column_type::enum_: case column_type::set: case column_type::json: return true; default: return false; } } static error_code parse( field_view input, std::basic_string<char, std::char_traits<char>, Allocator>& output ) { if (input.kind() != field_kind::string) { return client_errc::static_row_parsing_error; } output = input.get_string(); return error_code(); } }; template <class Allocator> struct readable_field_traits<std::vector<unsigned char, Allocator>, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "blob"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::binary: case column_type::varbinary: case column_type::blob: case column_type::geometry: case column_type::unknown: return true; default: return false; } } static error_code parse(field_view input, std::vector<unsigned char, Allocator>& output) { if (input.kind() != field_kind::blob) { return client_errc::static_row_parsing_error; } auto view = input.get_blob(); output.assign(view.begin(), view.end()); return error_code(); } }; template <> struct readable_field_traits<date, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "date"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::date; } static error_code parse(field_view input, date& output) { if (input.kind() != field_kind::date) { return client_errc::static_row_parsing_error; } output = input.get_date(); return error_code(); } }; template <> struct readable_field_traits<datetime, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "datetime"; static bool meta_check(meta_check_context& ctx) { switch (ctx.current_meta().type()) { case column_type::datetime: case column_type::timestamp: return true; default: return false; } } static error_code parse(field_view input, datetime& output) { if (input.kind() != field_kind::datetime) { return client_errc::static_row_parsing_error; } output = input.get_datetime(); return error_code(); } }; template <> struct readable_field_traits<time, void> { static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = "time"; static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::time; } static error_code parse(field_view input, time& output) { if (input.kind() != field_kind::time) { return client_errc::static_row_parsing_error; } output = input.get_time(); return error_code(); } }; // std::optional<T> and boost::optional<T>. To avoid dependencies, // this is achieved through a "concept" template <class T, class = void> struct is_readable_optional : std::false_type { }; template <class T> struct is_readable_optional< T, void_t< typename std::enable_if< std::is_same<decltype(std::declval<T&>().value()), typename T::value_type&>::value>::type, decltype(std::declval<T&>().emplace()), // T should be default constructible decltype(std::declval<T&>().reset())>> : std::true_type { }; template <class T> struct readable_field_traits< T, typename std::enable_if< is_readable_optional<T>::value && readable_field_traits<typename T::value_type>::is_supported>::type> { using value_type = typename T::value_type; static constexpr bool is_supported = true; static BOOST_INLINE_CONSTEXPR const char* type_name = readable_field_traits<value_type>::type_name; static bool meta_check(meta_check_context& ctx) { ctx.set_nullability_checked(); return readable_field_traits<value_type>::meta_check(ctx); } static error_code parse(field_view input, T& output) { if (input.is_null()) { output.reset(); return error_code(); } else { output.emplace(); return readable_field_traits<value_type>::parse(input, output.value()); } } }; template <class T> struct is_readable_field { static constexpr bool value = readable_field_traits<T>::is_supported; }; template <typename ReadableField> void meta_check_field_impl(meta_check_context& ctx) { using traits_t = readable_field_traits<ReadableField>; // Verify that the field is present if (ctx.is_current_field_absent()) { ctx.add_field_absent_error(); return; } // Perform the check bool ok = traits_t::meta_check(ctx); if (!ok) { ctx.add_type_mismatch_error(traits_t::type_name); } // Check nullability if (!ctx.nullability_checked() && !ctx.current_meta().is_not_null()) { ctx.add_nullability_error(); } } template <typename ReadableField> void meta_check_field(meta_check_context& ctx) { static_assert(is_readable_field<ReadableField>::value, "Should be a ReadableField"); meta_check_field_impl<ReadableField>(ctx); ctx.advance(); } } // namespace detail } // namespace mysql } // namespace boost #endif