123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- //
- // Copyright (c) 2019-2023 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 BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
- #define BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
- #pragma once
- #include <asio2/bho/mysql/blob_view.hpp>
- #include <asio2/bho/mysql/datetime.hpp>
- #include <asio2/bho/mysql/field_view.hpp>
- #include <asio2/bho/mysql/metadata.hpp>
- #include <asio2/bho/mysql/string_view.hpp>
- #include <asio2/bho/mysql/detail/config.hpp>
- #include <asio2/bho/mysql/detail/datetime.hpp>
- #include <asio2/bho/mysql/impl/internal/protocol/bit_deserialization.hpp>
- #include <asio2/bho/mysql/impl/internal/protocol/constants.hpp>
- #include <asio2/bho/mysql/impl/internal/protocol/deserialize_text_field.hpp>
- #include <asio2/bho/mysql/impl/internal/protocol/serialization.hpp>
- #include <asio2/bho/assert.hpp>
- #include <cstdlib>
- #include <cmath>
- #include <cstddef>
- #include <cstdlib>
- #include <type_traits>
- namespace bho {
- namespace mysql {
- namespace detail {
- template <typename Target, typename CharacterT>
- inline bool try_lexical_convert(const CharacterT* chars, std::size_t count, Target& result)
- {
- using T = typename std::remove_const_t<std::remove_reference_t<Target>>;
- std::string str(chars, chars + count);
- CharacterT* end = nullptr;
- if constexpr (std::is_integral_v<T>)
- {
- result = T(std::strtoull(str.data(), &end, 10));
- }
- else if constexpr (std::is_floating_point_v<T>)
- {
- result = T(std::strtold(str.data(), &end));
- }
- else
- {
- static_assert(!std::is_same_v<T, T>);
- }
- return end == (CharacterT*)(str.data() + count);
- }
- #ifdef BHO_MSVC
- #pragma warning(push)
- #pragma warning(disable : 4996) // MSVC doesn't like my sscanf's
- #endif
- // Constants
- BHO_MYSQL_STATIC_IF_COMPILED constexpr unsigned max_decimals = 6u;
- namespace textc {
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t year_sz = 4;
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t month_sz = 2;
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t day_sz = 2;
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t hours_min_sz = 2; // in TIME, it may be longer
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mins_sz = 2;
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t secs_sz = 2;
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t date_sz = year_sz + month_sz + day_sz + 2; // delimiters
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_min_sz = hours_min_sz + mins_sz + secs_sz +
- 2; // delimiters
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_max_sz = time_min_sz + max_decimals +
- 3; // sign, period, hour extra character
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_min_sz = date_sz + time_min_sz +
- 1; // delimiter
- BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_max_sz = datetime_min_sz + max_decimals +
- 1; // period
- BHO_MYSQL_STATIC_IF_COMPILED constexpr unsigned time_max_hour = 838;
- } // namespace textc
- // Integers
- template <class T>
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_int_impl(string_view from, field_view& to) noexcept
- {
- T v;
- bool ok = try_lexical_convert(from.data(), from.size(), v);
- if (!ok)
- return deserialize_errc::protocol_value_error;
- to = field_view(v);
- return deserialize_errc::ok;
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_int(string_view from, field_view& to, const metadata& meta) noexcept
- {
- return meta.is_unsigned() ? deserialize_text_value_int_impl<std::uint64_t>(from, to)
- : deserialize_text_value_int_impl<std::int64_t>(from, to);
- }
- // Floating points
- template <class T>
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_float(string_view from, field_view& to) noexcept
- {
- T val;
- bool ok = try_lexical_convert(from.data(), from.size(), val);
- if (!ok || std::isnan(val) || std::isinf(val)) // SQL std forbids these values
- return deserialize_errc::protocol_value_error;
- to = field_view(val);
- return deserialize_errc::ok;
- }
- // Strings
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_string(string_view from, field_view& to) noexcept
- {
- to = field_view(from);
- return deserialize_errc::ok;
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_blob(string_view from, field_view& to) noexcept
- {
- to = field_view(to_span(from));
- return deserialize_errc::ok;
- }
- // Date/time types
- BHO_MYSQL_STATIC_OR_INLINE unsigned sanitize_decimals(unsigned decimals) noexcept
- {
- return (std::min)(decimals, max_decimals);
- }
- // Computes the meaning of the parsed microsecond number, taking into
- // account decimals (85 with 2 decimals means 850000us)
- BHO_MYSQL_STATIC_OR_INLINE unsigned compute_micros(unsigned parsed_micros, unsigned decimals) noexcept
- {
- return parsed_micros * static_cast<unsigned>(std::pow(10, max_decimals - decimals));
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc deserialize_text_ymd(string_view from, date& to)
- {
- using namespace textc;
- // Size check
- if (from.size() != date_sz)
- return deserialize_errc::protocol_value_error;
- // Copy to a NULL-terminated buffer
- char buffer[date_sz + 1]{};
- std::memcpy(buffer, from.data(), from.size());
- // Parse individual components
- unsigned year, month, day;
- char extra_char;
- int parsed = sscanf(buffer, "%4u-%2u-%2u%c", &year, &month, &day, &extra_char);
- if (parsed != 3)
- return deserialize_errc::protocol_value_error;
- // Range check for individual components. MySQL doesn't allow invidiual components
- // to be out of range, although they may be zero or representing an invalid date
- if (year > max_year || month > max_month || day > max_day)
- return deserialize_errc::protocol_value_error;
- to = date(
- static_cast<std::uint16_t>(year),
- static_cast<std::uint8_t>(month),
- static_cast<std::uint8_t>(day)
- );
- return deserialize_errc::ok;
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_date(string_view from, field_view& to) noexcept
- {
- date d;
- auto err = deserialize_text_ymd(from, d);
- if (err != deserialize_errc::ok)
- return err;
- to = field_view(d);
- return deserialize_errc::ok;
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_datetime(string_view from, field_view& to, const metadata& meta) noexcept
- {
- using namespace textc;
- // Sanitize decimals
- unsigned decimals = sanitize_decimals(meta.decimals());
- // Length check
- std::size_t expected_size = datetime_min_sz + (decimals ? decimals + 1 : 0);
- if (from.size() != expected_size)
- return deserialize_errc::protocol_value_error;
- // Deserialize date part
- date d;
- auto err = deserialize_text_ymd(from.substr(0, date_sz), d);
- if (err != deserialize_errc::ok)
- return err;
- // Copy to NULL-terminated buffer
- constexpr std::size_t datetime_time_first = date_sz + 1; // date + space
- char buffer[datetime_max_sz - datetime_time_first + 1]{};
- std::memcpy(buffer, from.data() + datetime_time_first, from.size() - datetime_time_first);
- // Parse
- unsigned hours, minutes, seconds;
- unsigned micros = 0;
- char extra_char;
- if (decimals)
- {
- int parsed = sscanf(buffer, "%2u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, µs, &extra_char);
- if (parsed != 4)
- return deserialize_errc::protocol_value_error;
- micros = compute_micros(micros, decimals);
- }
- else
- {
- int parsed = sscanf(buffer, "%2u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
- if (parsed != 3)
- return deserialize_errc::protocol_value_error;
- }
- // Validity check. Although MySQL allows invalid and zero datetimes, it doesn't allow
- // individual components to be out of range.
- if (hours > max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
- {
- return deserialize_errc::protocol_value_error;
- }
- datetime dt(
- d.year(),
- d.month(),
- d.day(),
- static_cast<std::uint8_t>(hours),
- static_cast<std::uint8_t>(minutes),
- static_cast<std::uint8_t>(seconds),
- static_cast<std::uint32_t>(micros)
- );
- to = field_view(dt);
- return deserialize_errc::ok;
- }
- BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
- deserialize_text_value_time(string_view from, field_view& to, const metadata& meta) noexcept
- {
- using namespace textc;
- // Sanitize decimals
- unsigned decimals = sanitize_decimals(meta.decimals());
- // size check
- std::size_t actual_min_size = time_min_sz + (decimals ? decimals + 1 : 0);
- std::size_t actual_max_size = actual_min_size + 1 + 1; // hour extra character and sign
- BHO_ASSERT(actual_max_size <= time_max_sz);
- if (from.size() < actual_min_size || from.size() > actual_max_size)
- return deserialize_errc::protocol_value_error;
- // Copy to NULL-terminated buffer
- char buffer[time_max_sz + 1]{};
- memcpy(buffer, from.data(), from.size());
- // Sign
- bool is_negative = from[0] == '-';
- const char* first = is_negative ? buffer + 1 : buffer;
- // Parse it
- unsigned hours, minutes, seconds;
- unsigned micros = 0;
- char extra_char;
- if (decimals)
- {
- int parsed = sscanf(first, "%3u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, µs, &extra_char);
- if (parsed != 4)
- return deserialize_errc::protocol_value_error;
- micros = compute_micros(micros, decimals);
- }
- else
- {
- int parsed = sscanf(first, "%3u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
- if (parsed != 3)
- return deserialize_errc::protocol_value_error;
- }
- // Range check
- if (hours > time_max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
- {
- return deserialize_errc::protocol_value_error;
- }
- // Sum it
- auto res = std::chrono::hours(hours) + std::chrono::minutes(minutes) + std::chrono::seconds(seconds) +
- std::chrono::microseconds(micros);
- if (is_negative)
- {
- res = -res;
- }
- // Done
- to = field_view(res);
- return deserialize_errc::ok;
- }
- } // namespace detail
- } // namespace mysql
- } // namespace bho
- bho::mysql::detail::deserialize_errc bho::mysql::detail::deserialize_text_field(
- string_view from,
- const metadata& meta,
- field_view& output
- )
- {
- switch (meta.type())
- {
- case column_type::tinyint:
- case column_type::smallint:
- case column_type::mediumint:
- case column_type::int_:
- case column_type::bigint:
- case column_type::year: return deserialize_text_value_int(from, output, meta);
- case column_type::bit: return deserialize_bit(from, output);
- case column_type::float_: return deserialize_text_value_float<float>(from, output);
- case column_type::double_: return deserialize_text_value_float<double>(from, output);
- case column_type::timestamp:
- case column_type::datetime: return deserialize_text_value_datetime(from, output, meta);
- case column_type::date: return deserialize_text_value_date(from, output);
- case column_type::time: return deserialize_text_value_time(from, output, meta);
- // True string types
- case column_type::char_:
- case column_type::varchar:
- case column_type::text:
- case column_type::enum_:
- case column_type::set:
- case column_type::decimal:
- case column_type::json: return deserialize_text_value_string(from, output);
- // Blobs and anything else
- case column_type::binary:
- case column_type::varbinary:
- case column_type::blob:
- case column_type::geometry:
- default: return deserialize_text_value_blob(from, output);
- }
- }
- #endif
|