diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cee6e26..336d21c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,6 @@ add_executable( test/capabilities.cpp test/auth.cpp test/metadata.cpp - test/datetime.cpp ) target_link_libraries( unittests diff --git a/include/mysql/connection.hpp b/include/mysql/connection.hpp index c2d0cc04..7d2e3695 100644 --- a/include/mysql/connection.hpp +++ b/include/mysql/connection.hpp @@ -5,6 +5,7 @@ #include "mysql/impl/handshake.hpp" #include "mysql/impl/basic_types.hpp" #include "mysql/error.hpp" +#include "mysql/resultset.hpp" namespace mysql { @@ -14,8 +15,10 @@ using connection_params = detail::handshake_params; // TODO: do we think this in template > class connection { + using channel_type = detail::channel; + Stream next_level_; - detail::channel channel_; + channel_type channel_; detail::bytestring buffer_; public: template @@ -35,7 +38,12 @@ public: BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) async_handshake(const connection_params& params, CompletionToken&& token); + resultset query(std::string_view query_string, error_code&); + resultset query(std::string_view query_string); + template + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, resultset)) + async_query(std::string_view query_string, CompletionToken&& token); }; } diff --git a/include/mysql/datetime.hpp b/include/mysql/datetime.hpp deleted file mode 100644 index ec5257ba..00000000 --- a/include/mysql/datetime.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef INCLUDE_MYSQL_DATETIME_HPP_ -#define INCLUDE_MYSQL_DATETIME_HPP_ - -#include -#include -#include -#include - -namespace mysql -{ - -class datetime -{ - std::uint16_t year_ {}; - std::uint8_t month_ {}; - std::uint8_t day_ {}; - std::uint8_t hour_ {}; - std::uint8_t minute_ {}; - std::uint8_t second_ {}; - std::uint32_t microsecond_ {}; -public: - constexpr datetime() = default; - constexpr datetime(std::uint16_t years, std::uint8_t month, std::uint8_t day, - std::uint8_t hour=0, std::uint8_t min=0, std::uint8_t seconds=0, std::uint32_t micro=0) noexcept; - - constexpr std::uint16_t year() const noexcept { return year_; } - constexpr std::uint8_t month() const noexcept { return month_; } - constexpr std::uint8_t day() const noexcept { return day_; } - constexpr std::uint8_t hour() const noexcept { return hour_; } - constexpr std::uint8_t minute() const noexcept { return minute_; } - constexpr std::uint8_t second() const noexcept { return second_; } - constexpr std::uint32_t microsecond() const noexcept { return microsecond_; } - - constexpr void set_year(std::uint16_t value) noexcept { year_ = value; } - constexpr void set_month(std::uint8_t value) noexcept { month_ = value; } - constexpr void set_day(std::uint8_t value) noexcept { day_ = value; } - constexpr void set_hour(std::uint8_t value) noexcept { hour_ = value; } - constexpr void set_minute(std::uint8_t value) noexcept { minute_ = value; } - constexpr void set_second(std::uint8_t value) noexcept { second_ = value; } - constexpr void set_microsecond(std::uint32_t value) noexcept { microsecond_ = value; } - - constexpr bool operator==(const datetime& rhs) const noexcept; - constexpr bool operator!=(const datetime& rhs) const noexcept { return !(*this==rhs); } - - static constexpr std::size_t max_string_size = 4 + 2*5 + 6 + 6 + 1; - bool from_string(std::string_view value); - std::string to_string() const; - void to_string(char* to) const noexcept; - - // TODO: provide compatibilitiy with some well-known system -}; - -std::ostream& operator<<(std::ostream& os, const datetime& value); - -} - -#include "mysql/impl/datetime_impl.hpp" - -#endif /* INCLUDE_MYSQL_DATETIME_HPP_ */ diff --git a/include/mysql/impl/connection_impl.hpp b/include/mysql/impl/connection_impl.hpp index 2608a375..67fed963 100644 --- a/include/mysql/impl/connection_impl.hpp +++ b/include/mysql/impl/connection_impl.hpp @@ -2,8 +2,10 @@ #define INCLUDE_MYSQL_IMPL_CONNECTION_IMPL_HPP_ #include "mysql/impl/handshake.hpp" +#include "mysql/impl/query.hpp" #include +// Handshake template void mysql::connection::handshake( const connection_params& params, @@ -40,6 +42,31 @@ mysql::connection::async_handshake( ); } +// Query +template +mysql::resultset::channel_type, Allocator> +mysql::connection::query( + std::string_view query_string, + error_code& err +) +{ + resultset res; + detail::execute_query(channel_, query_string, res, err); + return res; +} + +template +mysql::resultset::channel_type, Allocator> +mysql::connection::query( + std::string_view query_string +) +{ + resultset res; + error_code err; + detail::execute_query(channel_, query_string, res, err); + detail::check_error_code(err); + return res; +} #endif /* INCLUDE_MYSQL_IMPL_CONNECTION_IMPL_HPP_ */ diff --git a/include/mysql/impl/datetime_impl.hpp b/include/mysql/impl/datetime_impl.hpp deleted file mode 100644 index fd0fd56c..00000000 --- a/include/mysql/impl/datetime_impl.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef INCLUDE_MYSQL_IMPL_DATETIME_IMPL_HPP_ -#define INCLUDE_MYSQL_IMPL_DATETIME_IMPL_HPP_ - -#include -#include -#include - -constexpr mysql::datetime::datetime( - std::uint16_t years, - std::uint8_t month, - std::uint8_t day, - std::uint8_t hour, - std::uint8_t min, - std::uint8_t seconds, - std::uint32_t micro -) noexcept : - year_(years), - month_(month), - day_(day), - hour_(hour), - minute_(min), - second_(seconds), - microsecond_(micro) -{ -} - -constexpr bool mysql::datetime::operator==( - const datetime& rhs -) const noexcept -{ - return year_ == rhs.year_ && - month_ == rhs.month_ && - day_ == rhs.day_ && - hour_ == rhs.hour_ && - minute_ == rhs.minute_ && - second_ == rhs.second_ && - microsecond_ == rhs.microsecond_; -} - -bool mysql::datetime::from_string( - std::string_view value -) -{ - if (value.size() > max_string_size - 1) return false; - char buffer [max_string_size] {}; - memcpy(buffer, value.data(), value.size()); - unsigned years {}, months {}, days {}, hours {}, mins {}, seconds {}, microseconds {}; - int elms_parsed = sscanf(buffer, "%u-%u-%u %u:%u:%u.%u", - &years, &months, &days, &hours, &mins, &seconds, µseconds); - if (elms_parsed < 3 || - months > 12 || - days > 31 || - hours > 24 || - mins > 60 || - seconds > 60 || - microseconds > 1000000) - { - return false; - } - year_ = years; - month_ = months; - day_ = days; - hour_ = hours; - minute_ = mins; - second_ = seconds; - microsecond_ = microseconds; - return true; -} - -inline void mysql::datetime::to_string( - char* to -) const noexcept -{ - unsigned years = std::min(static_cast(year()), 9999u); - unsigned months = std::min(month(), std::uint8_t(12)); - unsigned days = std::min(day(), std::uint8_t(31)); - unsigned hrs = std::min(hour(), std::uint8_t(24)); - unsigned mins = std::min(minute(), std::uint8_t(60)); - unsigned secs = std::min(second(), std::uint8_t(60)); - unsigned micros = std::min(microsecond(), 999999u); - int result = snprintf(to, max_string_size, "%4u-%2u-%2u %2u:%2u:%2u.%6u", - years, months, days, hrs, mins, secs, micros); - assert(result == (max_string_size - 1)); -} - -inline std::string mysql::datetime::to_string() const -{ - std::string res (max_string_size, 0); - to_string(res.data()); - res.pop_back(); - return res; -} - -inline std::ostream& mysql::operator<<( - std::ostream& os, - const datetime& value -) -{ - char buffer [datetime::max_string_size]; - value.to_string(buffer); - return os << buffer; -} - -#endif /* INCLUDE_MYSQL_IMPL_DATETIME_IMPL_HPP_ */ diff --git a/include/mysql/impl/deserialize_row.hpp b/include/mysql/impl/deserialize_row.hpp new file mode 100644 index 00000000..5c7a4479 --- /dev/null +++ b/include/mysql/impl/deserialize_row.hpp @@ -0,0 +1,34 @@ +#ifndef INCLUDE_MYSQL_IMPL_DESERIALIZE_ROW_HPP_ +#define INCLUDE_MYSQL_IMPL_DESERIALIZE_ROW_HPP_ + +#include "mysql/error.hpp" +#include "mysql/value.hpp" +#include "mysql/metadata.hpp" +#include "mysql/value.hpp" +#include "mysql/impl/basic_serialization.hpp" +#include + +namespace mysql +{ +namespace detail +{ + +inline Error deserialize_text_value( + std::string_view from, + const field_metadata& meta, + value& output +); + +template +error_code deserialize_text_row( + DeserializationContext& ctx, + const resultset_metadata& meta, + std::vector& output +); + +} +} + +#include "mysql/impl/deserialize_row_impl.hpp" + +#endif /* INCLUDE_MYSQL_IMPL_DESERIALIZE_ROW_HPP_ */ diff --git a/include/mysql/impl/deserialize_row_impl.hpp b/include/mysql/impl/deserialize_row_impl.hpp new file mode 100644 index 00000000..449bc939 --- /dev/null +++ b/include/mysql/impl/deserialize_row_impl.hpp @@ -0,0 +1,218 @@ +#ifndef INCLUDE_MYSQL_IMPL_DESERIALIZE_ROW_IMPL_HPP_ +#define INCLUDE_MYSQL_IMPL_DESERIALIZE_ROW_IMPL_HPP_ + +#include +#include +#include +#include + +namespace mysql +{ +namespace detail +{ + +inline Error deserialize_text_value_impl( + std::string_view from, + date& to +) +{ + constexpr std::size_t size = 4 + 2 + 2 + 2; // year, month, day, separators + if (from.size() != size) return Error::protocol_value_error; + unsigned year, month, day; + char buffer [size + 1] {}; + memcpy(buffer, from.data(), from.size()); + int parsed = sscanf(buffer, "%4u-%2u-%2u", &year, &month, &day); + if (parsed != 3) return Error::protocol_value_error; + ::date::year_month_day result (::date::year(year)/::date::month(month)/::date::day(day)); + if (!result.ok()) return Error::protocol_value_error; + if (result > max_date || result < min_date) return Error::protocol_value_error; + to = result; + return Error::ok; +} + +inline Error deserialize_text_value_impl( + std::string_view from, + time& to, + unsigned decimals +) +{ + // size check + constexpr std::size_t min_size = 2 + 2 + 2 + 2; // hours, mins, seconds, no micros + constexpr std::size_t max_size = min_size + 1 + 7; // hour extra character and micros + decimals = std::min(decimals, 6u); + if (from.size() < min_size || from.size() > max_size) return Error::protocol_value_error; + + // Parse it + int hours; + unsigned minutes, seconds, micros = 0; + char buffer [max_size + 1] {}; + memcpy(buffer, from.data(), from.size()); + int parsed = decimals ? sscanf(buffer, "%3d:%2u:%2u.%6u", &hours, &minutes, &seconds, µs) : + sscanf(buffer, "%3d:%2u:%2u", &hours, &minutes, &seconds); + if ((decimals && parsed != 4) || parsed != 3) return Error::protocol_value_error; + micros *= std::pow(10, 6 - decimals); + bool is_negative = hours < 0; + hours = std::abs(hours); + + // 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; + } + + // Range check + if (res > max_time || res < min_time) return Error::protocol_value_error; + + to = res; + return Error::ok; +} + +inline Error deserialize_text_value_impl( + std::string_view from, + datetime& to, + unsigned decimals +) +{ + // Length check + constexpr std::size_t min_size = 4 + 5*2 + 5; // year, month, day, hour, minute, seconds, separators + constexpr std::size_t max_size = min_size + 7; + decimals = std::min(decimals, 6u); + std::size_t expected_size = min_size + (decimals ? decimals + 1 : 0); + if (from.size() != expected_size) return Error::protocol_value_error; + + // Date part + date dt; + auto err = deserialize_text_value_impl(from.substr(0, 10), dt); + if (err != Error::ok) return err; + + // Time of day part + time time_of_day; + err = deserialize_text_value_impl(from.substr(11), time_of_day, decimals); + if (err != Error::ok) return err; + constexpr auto max_time_of_day = std::chrono::hours(24) - std::chrono::microseconds(1); + if (time_of_day < std::chrono::seconds(0) || time_of_day > max_time_of_day) return Error::protocol_value_error; + + // Sum it up + to = dt + time_of_day; + return Error::ok; +} + + +template +std::enable_if_t, Error> +deserialize_text_value_impl(std::string_view from, T& to) +{ + bool ok = boost::conversion::try_lexical_convert(from.data(), from.size(), to); + return ok ? Error::ok : Error::protocol_value_error; +} + +Error deserialize_text_value_impl(std::string_view from, std::string_view& to) +{ + to = from; + return Error::ok; +} + +Error deserialize_text_value_impl(std::string_view from, std::nullptr_t& to) +{ + return Error::ok; +} + +template +Error deserialize_text_value_to_variant(std::string_view from, value& to, Args&&... args) +{ + T value; + auto err = deserialize_text_value_impl(from, value, std::forward(args)...); + if (err == Error::ok) + { + to = value; + } + return err; +} + +} +} + +inline mysql::Error mysql::detail::deserialize_text_value( + std::string_view from, + const field_metadata& meta, + value& output +) +{ + switch (meta.type()) + { + case field_type::decimal: + case field_type::varchar: + case field_type::bit: + case field_type::newdecimal: + case field_type::enum_: + case field_type::set: + case field_type::tiny_blob: + case field_type::medium_blob: + case field_type::long_blob: + case field_type::blob: + case field_type::var_string: + case field_type::string: + case field_type::geometry: + return deserialize_text_value_to_variant(from, output); + case field_type::tiny: + return meta.is_unsigned() ? + deserialize_text_value_to_variant(from, output) : + deserialize_text_value_to_variant(from, output); + case field_type::short_: + return meta.is_unsigned() ? + deserialize_text_value_to_variant(from, output) : + deserialize_text_value_to_variant(from, output); + case field_type::int24: + case field_type::long_: + case field_type::year: + return meta.is_unsigned() ? + deserialize_text_value_to_variant(from, output) : + deserialize_text_value_to_variant(from, output); + case field_type::longlong: + return meta.is_unsigned() ? + deserialize_text_value_to_variant(from, output) : + deserialize_text_value_to_variant(from, output); + case field_type::float_: + return deserialize_text_value_to_variant(from, output); + case field_type::double_: + return deserialize_text_value_to_variant(from, output); + case field_type::null: + return deserialize_text_value_to_variant(from, output); + case field_type::timestamp: + case field_type::datetime: + return deserialize_text_value_to_variant(from, output, meta.decimals()); + case field_type::date: + return deserialize_text_value_to_variant(from, output); + case field_type::time: + return deserialize_text_value_to_variant