deserialize_text_field.ipp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //
  2. // Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
  8. #define BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
  9. #pragma once
  10. #include <asio2/bho/mysql/blob_view.hpp>
  11. #include <asio2/bho/mysql/datetime.hpp>
  12. #include <asio2/bho/mysql/field_view.hpp>
  13. #include <asio2/bho/mysql/metadata.hpp>
  14. #include <asio2/bho/mysql/string_view.hpp>
  15. #include <asio2/bho/mysql/detail/config.hpp>
  16. #include <asio2/bho/mysql/detail/datetime.hpp>
  17. #include <asio2/bho/mysql/impl/internal/protocol/bit_deserialization.hpp>
  18. #include <asio2/bho/mysql/impl/internal/protocol/constants.hpp>
  19. #include <asio2/bho/mysql/impl/internal/protocol/deserialize_text_field.hpp>
  20. #include <asio2/bho/mysql/impl/internal/protocol/serialization.hpp>
  21. #include <asio2/bho/assert.hpp>
  22. #include <cstdlib>
  23. #include <cmath>
  24. #include <cstddef>
  25. #include <cstdlib>
  26. #include <type_traits>
  27. namespace bho {
  28. namespace mysql {
  29. namespace detail {
  30. template <typename Target, typename CharacterT>
  31. inline bool try_lexical_convert(const CharacterT* chars, std::size_t count, Target& result)
  32. {
  33. using T = typename std::remove_const_t<std::remove_reference_t<Target>>;
  34. std::string str(chars, chars + count);
  35. CharacterT* end = nullptr;
  36. if constexpr (std::is_integral_v<T>)
  37. {
  38. result = T(std::strtoull(str.data(), &end, 10));
  39. }
  40. else if constexpr (std::is_floating_point_v<T>)
  41. {
  42. result = T(std::strtold(str.data(), &end));
  43. }
  44. else
  45. {
  46. static_assert(!std::is_same_v<T, T>);
  47. }
  48. return end == (CharacterT*)(str.data() + count);
  49. }
  50. #ifdef BHO_MSVC
  51. #pragma warning(push)
  52. #pragma warning(disable : 4996) // MSVC doesn't like my sscanf's
  53. #endif
  54. // Constants
  55. BHO_MYSQL_STATIC_IF_COMPILED constexpr unsigned max_decimals = 6u;
  56. namespace textc {
  57. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t year_sz = 4;
  58. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t month_sz = 2;
  59. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t day_sz = 2;
  60. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t hours_min_sz = 2; // in TIME, it may be longer
  61. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mins_sz = 2;
  62. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t secs_sz = 2;
  63. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t date_sz = year_sz + month_sz + day_sz + 2; // delimiters
  64. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_min_sz = hours_min_sz + mins_sz + secs_sz +
  65. 2; // delimiters
  66. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_max_sz = time_min_sz + max_decimals +
  67. 3; // sign, period, hour extra character
  68. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_min_sz = date_sz + time_min_sz +
  69. 1; // delimiter
  70. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_max_sz = datetime_min_sz + max_decimals +
  71. 1; // period
  72. BHO_MYSQL_STATIC_IF_COMPILED constexpr unsigned time_max_hour = 838;
  73. } // namespace textc
  74. // Integers
  75. template <class T>
  76. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  77. deserialize_text_value_int_impl(string_view from, field_view& to) noexcept
  78. {
  79. T v;
  80. bool ok = try_lexical_convert(from.data(), from.size(), v);
  81. if (!ok)
  82. return deserialize_errc::protocol_value_error;
  83. to = field_view(v);
  84. return deserialize_errc::ok;
  85. }
  86. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  87. deserialize_text_value_int(string_view from, field_view& to, const metadata& meta) noexcept
  88. {
  89. return meta.is_unsigned() ? deserialize_text_value_int_impl<std::uint64_t>(from, to)
  90. : deserialize_text_value_int_impl<std::int64_t>(from, to);
  91. }
  92. // Floating points
  93. template <class T>
  94. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  95. deserialize_text_value_float(string_view from, field_view& to) noexcept
  96. {
  97. T val;
  98. bool ok = try_lexical_convert(from.data(), from.size(), val);
  99. if (!ok || std::isnan(val) || std::isinf(val)) // SQL std forbids these values
  100. return deserialize_errc::protocol_value_error;
  101. to = field_view(val);
  102. return deserialize_errc::ok;
  103. }
  104. // Strings
  105. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  106. deserialize_text_value_string(string_view from, field_view& to) noexcept
  107. {
  108. to = field_view(from);
  109. return deserialize_errc::ok;
  110. }
  111. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  112. deserialize_text_value_blob(string_view from, field_view& to) noexcept
  113. {
  114. to = field_view(to_span(from));
  115. return deserialize_errc::ok;
  116. }
  117. // Date/time types
  118. BHO_MYSQL_STATIC_OR_INLINE unsigned sanitize_decimals(unsigned decimals) noexcept
  119. {
  120. return (std::min)(decimals, max_decimals);
  121. }
  122. // Computes the meaning of the parsed microsecond number, taking into
  123. // account decimals (85 with 2 decimals means 850000us)
  124. BHO_MYSQL_STATIC_OR_INLINE unsigned compute_micros(unsigned parsed_micros, unsigned decimals) noexcept
  125. {
  126. return parsed_micros * static_cast<unsigned>(std::pow(10, max_decimals - decimals));
  127. }
  128. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc deserialize_text_ymd(string_view from, date& to)
  129. {
  130. using namespace textc;
  131. // Size check
  132. if (from.size() != date_sz)
  133. return deserialize_errc::protocol_value_error;
  134. // Copy to a NULL-terminated buffer
  135. char buffer[date_sz + 1]{};
  136. std::memcpy(buffer, from.data(), from.size());
  137. // Parse individual components
  138. unsigned year, month, day;
  139. char extra_char;
  140. int parsed = sscanf(buffer, "%4u-%2u-%2u%c", &year, &month, &day, &extra_char);
  141. if (parsed != 3)
  142. return deserialize_errc::protocol_value_error;
  143. // Range check for individual components. MySQL doesn't allow invidiual components
  144. // to be out of range, although they may be zero or representing an invalid date
  145. if (year > max_year || month > max_month || day > max_day)
  146. return deserialize_errc::protocol_value_error;
  147. to = date(
  148. static_cast<std::uint16_t>(year),
  149. static_cast<std::uint8_t>(month),
  150. static_cast<std::uint8_t>(day)
  151. );
  152. return deserialize_errc::ok;
  153. }
  154. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  155. deserialize_text_value_date(string_view from, field_view& to) noexcept
  156. {
  157. date d;
  158. auto err = deserialize_text_ymd(from, d);
  159. if (err != deserialize_errc::ok)
  160. return err;
  161. to = field_view(d);
  162. return deserialize_errc::ok;
  163. }
  164. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  165. deserialize_text_value_datetime(string_view from, field_view& to, const metadata& meta) noexcept
  166. {
  167. using namespace textc;
  168. // Sanitize decimals
  169. unsigned decimals = sanitize_decimals(meta.decimals());
  170. // Length check
  171. std::size_t expected_size = datetime_min_sz + (decimals ? decimals + 1 : 0);
  172. if (from.size() != expected_size)
  173. return deserialize_errc::protocol_value_error;
  174. // Deserialize date part
  175. date d;
  176. auto err = deserialize_text_ymd(from.substr(0, date_sz), d);
  177. if (err != deserialize_errc::ok)
  178. return err;
  179. // Copy to NULL-terminated buffer
  180. constexpr std::size_t datetime_time_first = date_sz + 1; // date + space
  181. char buffer[datetime_max_sz - datetime_time_first + 1]{};
  182. std::memcpy(buffer, from.data() + datetime_time_first, from.size() - datetime_time_first);
  183. // Parse
  184. unsigned hours, minutes, seconds;
  185. unsigned micros = 0;
  186. char extra_char;
  187. if (decimals)
  188. {
  189. int parsed = sscanf(buffer, "%2u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, &micros, &extra_char);
  190. if (parsed != 4)
  191. return deserialize_errc::protocol_value_error;
  192. micros = compute_micros(micros, decimals);
  193. }
  194. else
  195. {
  196. int parsed = sscanf(buffer, "%2u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
  197. if (parsed != 3)
  198. return deserialize_errc::protocol_value_error;
  199. }
  200. // Validity check. Although MySQL allows invalid and zero datetimes, it doesn't allow
  201. // individual components to be out of range.
  202. if (hours > max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
  203. {
  204. return deserialize_errc::protocol_value_error;
  205. }
  206. datetime dt(
  207. d.year(),
  208. d.month(),
  209. d.day(),
  210. static_cast<std::uint8_t>(hours),
  211. static_cast<std::uint8_t>(minutes),
  212. static_cast<std::uint8_t>(seconds),
  213. static_cast<std::uint32_t>(micros)
  214. );
  215. to = field_view(dt);
  216. return deserialize_errc::ok;
  217. }
  218. BHO_MYSQL_STATIC_OR_INLINE deserialize_errc
  219. deserialize_text_value_time(string_view from, field_view& to, const metadata& meta) noexcept
  220. {
  221. using namespace textc;
  222. // Sanitize decimals
  223. unsigned decimals = sanitize_decimals(meta.decimals());
  224. // size check
  225. std::size_t actual_min_size = time_min_sz + (decimals ? decimals + 1 : 0);
  226. std::size_t actual_max_size = actual_min_size + 1 + 1; // hour extra character and sign
  227. BHO_ASSERT(actual_max_size <= time_max_sz);
  228. if (from.size() < actual_min_size || from.size() > actual_max_size)
  229. return deserialize_errc::protocol_value_error;
  230. // Copy to NULL-terminated buffer
  231. char buffer[time_max_sz + 1]{};
  232. memcpy(buffer, from.data(), from.size());
  233. // Sign
  234. bool is_negative = from[0] == '-';
  235. const char* first = is_negative ? buffer + 1 : buffer;
  236. // Parse it
  237. unsigned hours, minutes, seconds;
  238. unsigned micros = 0;
  239. char extra_char;
  240. if (decimals)
  241. {
  242. int parsed = sscanf(first, "%3u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, &micros, &extra_char);
  243. if (parsed != 4)
  244. return deserialize_errc::protocol_value_error;
  245. micros = compute_micros(micros, decimals);
  246. }
  247. else
  248. {
  249. int parsed = sscanf(first, "%3u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
  250. if (parsed != 3)
  251. return deserialize_errc::protocol_value_error;
  252. }
  253. // Range check
  254. if (hours > time_max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
  255. {
  256. return deserialize_errc::protocol_value_error;
  257. }
  258. // Sum it
  259. auto res = std::chrono::hours(hours) + std::chrono::minutes(minutes) + std::chrono::seconds(seconds) +
  260. std::chrono::microseconds(micros);
  261. if (is_negative)
  262. {
  263. res = -res;
  264. }
  265. // Done
  266. to = field_view(res);
  267. return deserialize_errc::ok;
  268. }
  269. } // namespace detail
  270. } // namespace mysql
  271. } // namespace bho
  272. bho::mysql::detail::deserialize_errc bho::mysql::detail::deserialize_text_field(
  273. string_view from,
  274. const metadata& meta,
  275. field_view& output
  276. )
  277. {
  278. switch (meta.type())
  279. {
  280. case column_type::tinyint:
  281. case column_type::smallint:
  282. case column_type::mediumint:
  283. case column_type::int_:
  284. case column_type::bigint:
  285. case column_type::year: return deserialize_text_value_int(from, output, meta);
  286. case column_type::bit: return deserialize_bit(from, output);
  287. case column_type::float_: return deserialize_text_value_float<float>(from, output);
  288. case column_type::double_: return deserialize_text_value_float<double>(from, output);
  289. case column_type::timestamp:
  290. case column_type::datetime: return deserialize_text_value_datetime(from, output, meta);
  291. case column_type::date: return deserialize_text_value_date(from, output);
  292. case column_type::time: return deserialize_text_value_time(from, output, meta);
  293. // True string types
  294. case column_type::char_:
  295. case column_type::varchar:
  296. case column_type::text:
  297. case column_type::enum_:
  298. case column_type::set:
  299. case column_type::decimal:
  300. case column_type::json: return deserialize_text_value_string(from, output);
  301. // Blobs and anything else
  302. case column_type::binary:
  303. case column_type::varbinary:
  304. case column_type::blob:
  305. case column_type::geometry:
  306. default: return deserialize_text_value_blob(from, output);
  307. }
  308. }
  309. #endif