From a65e86af1dec4fcac89b9732ef14bfb60e493492 Mon Sep 17 00:00:00 2001 From: ruben Date: Sat, 18 Jan 2020 20:05:21 +0000 Subject: [PATCH] Added error_info --- .travis.yml | 65 +++++- TODO.txt | 14 +- examples/query_async.cpp | 34 ++-- include/mysql/connection.hpp | 8 +- include/mysql/error.hpp | 15 ++ include/mysql/impl/connection.ipp | 37 +++- include/mysql/impl/error.hpp | 6 +- include/mysql/impl/handshake.hpp | 7 +- include/mysql/impl/handshake.ipp | 43 ++-- include/mysql/impl/messages.hpp | 2 +- include/mysql/impl/messages.ipp | 4 +- include/mysql/impl/query.hpp | 11 +- include/mysql/impl/query.ipp | 51 +++-- .../impl/{resultset.hpp => resultset.ipp} | 68 +++++-- include/mysql/resultset.hpp | 14 +- test/CMakeLists.txt | 6 +- test/common/test_common.hpp | 11 + test/integration/handshake.cpp | 35 ++-- test/integration/integration_test_common.hpp | 47 ++++- test/integration/query.cpp | 188 ++++++++++++------ test/integration/run_tests.sh | 6 +- 21 files changed, 469 insertions(+), 203 deletions(-) rename include/mysql/impl/{resultset.hpp => resultset.ipp} (80%) diff --git a/.travis.yml b/.travis.yml index 76213829..bb9a5bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,53 @@ matrix: compiler: gcc services: - mysql + env: + - CMAKE_OPTIONS=-DCMAKE_BUILD_TYPE=Debug + addons: + apt: + update: true + sources: + - sourceline: 'ppa:mhier/libboost-latest' + packages: + - boost1.70 + - os: linux + dist: bionic + sudo: true + compiler: gcc + services: + - mysql + env: + - CMAKE_OPTIONS=-DCMAKE_BUILD_TYPE=Release + addons: + apt: + update: true + sources: + - sourceline: 'ppa:mhier/libboost-latest' + packages: + - boost1.70 + - os: linux + dist: bionic + sudo: true + compiler: gcc + services: + - mysql + env: + - CMAKE_OPTIONS=-DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + addons: + apt: + update: true + sources: + - sourceline: 'ppa:mhier/libboost-latest' + packages: + - boost1.70 + - os: linux + dist: bionic + sudo: true + compiler: gcc + services: + - mysql + env: + - CMAKE_OPTIONS=-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ addons: apt: update: true @@ -27,12 +74,24 @@ matrix: before_install: - mysql.server start env: - - CMAKE_OPTIONS=-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl - + - CMAKE_OPTIONS=-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DCMAKE_BUILD_TYPE=Debug + - os: osx + osx_image: xcode11.3 + sudo: true + compiler: clang + addons: + homebrew: + packages: + - boost + - mysql + before_install: + - mysql.server start + env: + - CMAKE_OPTIONS=-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DCMAKE_BUILD_TYPE=Release script: - mkdir -p build - cd build - - cmake -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS .. + - cmake $CMAKE_OPTIONS .. - make -j CTEST_OUTPUT_ON_FAILURE=1 all test diff --git a/TODO.txt b/TODO.txt index 424049be..b24eee01 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,3 +1,14 @@ +error_info + Add error_info to all existing API + Document + Add error_info to examples + Add error_info to integ tests + Make error_info optional for synchronous (?) + Use error_info in sync exc + Add validation of exception messages + Error code descriptions + Review async testing with futures (it no longer tests the error_info parameter) + Verify that error_codes are cleared in sync errc stuff Prepared statements Multiresultset Text protocol @@ -9,9 +20,6 @@ Handshake Usability Incomplete query reads: how does this affect further queries? Metadata in rows: being able to index by name - Errors - Error code descriptions - Error text is lost in the protocol Iterators for sync resultset iteration Better interface for connection_params data structure Consideration of timezones diff --git a/examples/query_async.cpp b/examples/query_async.cpp index 9e3e4136..57cf3b58 100644 --- a/examples/query_async.cpp +++ b/examples/query_async.cpp @@ -27,11 +27,11 @@ void print_employee(const mysql::row& employee) << employee.values()[2] << " dollars yearly\n"; // salary (type double) } -void die_on_error(const mysql::error_code& err) +void die_on_error(const mysql::error_code& err, const mysql::error_info& info = mysql::error_info()) { if (err) { - std::cerr << "Error: " << err << std::endl; + std::cerr << "Error: " << err << ": " << info.message() << std::endl; exit(1); } } @@ -55,10 +55,10 @@ public: void connect() { - connection.next_level().async_connect(ep, [this](const mysql::error_code& err) { + connection.next_level().async_connect(ep, [this](mysql::error_code err) { die_on_error(err); - connection.async_handshake(conn_params, [this](const mysql::error_code& err) { - die_on_error(err); + connection.async_handshake(conn_params, [this](mysql::error_code err, const mysql::error_info& info) { + die_on_error(err, info); query_employees(); }); }); @@ -67,11 +67,13 @@ public: void query_employees() { const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; - connection.async_query(sql, [this](const mysql::error_code& err, mysql::tcp_resultset&& result) { - die_on_error(err); + connection.async_query(sql, [this](mysql::error_code err, const mysql::error_info& info, + mysql::tcp_resultset&& result + ) { + die_on_error(err, info); resultset = std::move(result); - resultset.async_fetch_all([this](const mysql::error_code& err, const auto& rows) { - die_on_error(err); + resultset.async_fetch_all([this](mysql::error_code err, const mysql::error_info& info, const auto& rows) { + die_on_error(err, info); for (const auto& employee: rows) { print_employee(employee); @@ -84,8 +86,9 @@ public: void update_slacker() { const char* sql = "UPDATE employee SET salary = 15000 WHERE last_name = 'Slacker'"; - connection.async_query(sql, [this](const mysql::error_code& err, [[maybe_unused]] mysql::tcp_resultset&& result) { - die_on_error(err); + connection.async_query(sql, [this](mysql::error_code err, const mysql::error_info& info, + [[maybe_unused]] mysql::tcp_resultset&& result) { + die_on_error(err, info); assert(result.fields().size() == 0); query_intern(); }); @@ -94,11 +97,12 @@ public: void query_intern() { const char* sql = "SELECT salary FROM employee WHERE last_name = 'Slacker'"; - connection.async_query(sql, [this](const mysql::error_code& err, mysql::tcp_resultset&& result) { - die_on_error(err); + connection.async_query(sql, [this](const mysql::error_code& err, mysql::error_info info, + mysql::tcp_resultset&& result) { + die_on_error(err, info); resultset = std::move(result); - resultset.async_fetch_all([](const mysql::error_code& err, const auto& rows) { - die_on_error(err); + resultset.async_fetch_all([](const mysql::error_code& err, mysql::error_info info, const auto& rows) { + die_on_error(err, info); assert(rows.size() == 1); [[maybe_unused]] auto salary = std::get(rows[0].values()[0]); assert(salary == 15000); diff --git a/include/mysql/connection.hpp b/include/mysql/connection.hpp index 86804f7a..4c0b7906 100644 --- a/include/mysql/connection.hpp +++ b/include/mysql/connection.hpp @@ -85,7 +85,7 @@ public: const Stream& next_level() const { return next_level_; } /// Performs the MySQL-level handshake (synchronous with error code version). - void handshake(const connection_params& params, error_code& ec); + void handshake(const connection_params& params, error_code& ec, error_info& info); /// Performs the MySQL-level handshake (synchronous with exceptions version). void handshake(const connection_params& params); @@ -96,7 +96,7 @@ public: * until the operation completes, as no copy is made by the library. */ template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info)) async_handshake(const connection_params& params, CompletionToken&& token); /** @@ -111,14 +111,14 @@ public: * \warning After query() has returned, you should read the entire resultset * before calling query() again. Otherwise, the results are undefined. */ - resultset query(std::string_view query_string, error_code&); + resultset query(std::string_view query_string, error_code&, error_info&); /// Executes a SQL text query (sync with exceptions version). resultset query(std::string_view query_string); /// Executes a SQL text query (async version). template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, resultset)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, resultset)) async_query(std::string_view query_string, CompletionToken&& token); }; diff --git a/include/mysql/error.hpp b/include/mysql/error.hpp index 58f8f1d5..c9b91117 100644 --- a/include/mysql/error.hpp +++ b/include/mysql/error.hpp @@ -2,6 +2,7 @@ #define MYSQL_ASIO_ERROR_HPP #include +#include namespace mysql { @@ -27,6 +28,20 @@ enum class Error : int /// An alias for boost::system error codes. using error_code = boost::system::error_code; +/// Additional information about error conditions +class error_info +{ + std::string msg_; +public: + error_info(std::string&& err = {}): msg_(std::move(err)) {} + const std::string& message() const noexcept { return msg_; } + void set_message(std::string&& err) { msg_ = std::move(err); } + void clear() noexcept { msg_.clear(); } +}; +inline bool operator==(const error_info& lhs, const error_info& rhs) noexcept { return lhs.message() == rhs.message(); } +inline bool operator!=(const error_info& lhs, const error_info& rhs) noexcept { return !(lhs==rhs); } +inline std::ostream& operator<<(std::ostream& os, const error_info& v) { return os << v.message(); } + } #include "mysql/impl/error.hpp" diff --git a/include/mysql/impl/connection.ipp b/include/mysql/impl/connection.ipp index 437b2724..3af7dfaf 100644 --- a/include/mysql/impl/connection.ipp +++ b/include/mysql/impl/connection.ipp @@ -28,10 +28,19 @@ inline handshake_params to_handshake_params( template void mysql::connection::handshake( const connection_params& params, - error_code& errc + error_code& errc, + error_info& info ) { - detail::hanshake(channel_, detail::to_handshake_params(params), buffer_, errc); + errc.clear(); + info.clear(); + detail::hanshake( + channel_, + detail::to_handshake_params(params), + buffer_, + errc, + info + ); // TODO: should we close() the stream in case of error? } @@ -41,13 +50,14 @@ void mysql::connection::handshake( ) { error_code errc; - handshake(params, errc); - detail::check_error_code(errc); + error_info info; + handshake(params, errc, info); + detail::check_error_code(errc, info); } template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code)) +BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, mysql::error_info)) mysql::connection::async_handshake( const connection_params& params, CompletionToken&& token @@ -65,11 +75,14 @@ mysql::connection::async_handshake( template mysql::resultset mysql::connection::query( std::string_view query_string, - error_code& err + error_code& err, + error_info& info ) { + err.clear(); + info.clear(); resultset res; - detail::execute_query(channel_, query_string, res, err); + detail::execute_query(channel_, query_string, res, err, info); return res; } @@ -80,14 +93,18 @@ mysql::resultset mysql::connection::query( { resultset res; error_code err; - detail::execute_query(channel_, query_string, res, err); - detail::check_error_code(err); + error_info info; + detail::execute_query(channel_, query_string, res, err, info); + detail::check_error_code(err, info); return res; } template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, mysql::resultset)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + void(mysql::error_code, mysql::error_info, mysql::resultset) +) mysql::connection::async_query( std::string_view query_string, CompletionToken&& token diff --git a/include/mysql/impl/error.hpp b/include/mysql/impl/error.hpp index bf7cc737..2fd36dfc 100644 --- a/include/mysql/impl/error.hpp +++ b/include/mysql/impl/error.hpp @@ -64,11 +64,11 @@ inline boost::system::error_code make_error_code(Error error) ); } -inline void check_error_code(const error_code& errc) +inline void check_error_code(const error_code& errc, const error_info& info) { if (errc) { - throw boost::system::system_error(errc); + throw boost::system::system_error(errc, info.message()); } } @@ -78,4 +78,4 @@ inline void check_error_code(const error_code& errc) -#endif \ No newline at end of file +#endif diff --git a/include/mysql/impl/handshake.hpp b/include/mysql/impl/handshake.hpp index 85895041..0937a159 100644 --- a/include/mysql/impl/handshake.hpp +++ b/include/mysql/impl/handshake.hpp @@ -26,11 +26,12 @@ void hanshake( ChannelType& channel, const handshake_params& params, bytestring& buffer, - error_code& err + error_code& err, + error_info& info ); template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) +BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info)) async_handshake( ChannelType& channel, const handshake_params& params, @@ -44,4 +45,4 @@ async_handshake( #include "mysql/impl/handshake.ipp" -#endif \ No newline at end of file +#endif diff --git a/include/mysql/impl/handshake.ipp b/include/mysql/impl/handshake.ipp index 4713aed9..31b7b63c 100644 --- a/include/mysql/impl/handshake.ipp +++ b/include/mysql/impl/handshake.ipp @@ -21,7 +21,8 @@ inline std::uint8_t get_collation_first_byte(collation value) inline error_code deserialize_handshake( boost::asio::const_buffer buffer, - handshake_packet& output + handshake_packet& output, + error_info& info ) { DeserializationContext ctx (boost::asio::buffer(buffer), capabilities()); @@ -33,7 +34,7 @@ inline error_code deserialize_handshake( } else if (msg_type == error_packet_header) { - return process_error_packet(ctx); + return process_error_packet(ctx, info); } else if (msg_type != handshake_protocol_version_10) { @@ -127,11 +128,11 @@ public: ); } - error_code process_handshake(bytestring& buffer) + error_code process_handshake(bytestring& buffer, error_info& info) { // Deserialize server greeting handshake_packet handshake; - auto err = deserialize_handshake(boost::asio::buffer(buffer), handshake); + auto err = deserialize_handshake(boost::asio::buffer(buffer), handshake, info); if (err) return err; // Check capabilities @@ -159,7 +160,8 @@ public: error_code process_handshake_server_response( bytestring& buffer, - bool& auth_complete + bool& auth_complete, + error_info& info ) { DeserializationContext ctx (boost::asio::buffer(buffer), negotiated_caps_); @@ -174,7 +176,7 @@ public: } else if (msg_type == error_packet_header) { - return process_error_packet(ctx); + return process_error_packet(ctx, info); } else if (msg_type != auth_switch_request_header) { @@ -200,7 +202,8 @@ public: } error_code process_auth_switch_response( - boost::asio::const_buffer buffer + boost::asio::const_buffer buffer, + error_info& info ) { DeserializationContext ctx (boost::asio::buffer(buffer), negotiated_caps_); @@ -208,7 +211,7 @@ public: if (err) return err; if (msg_type == error_packet_header) { - return process_error_packet(ctx); + return process_error_packet(ctx, info); } else if (msg_type != ok_packet_header) { @@ -228,9 +231,12 @@ void mysql::detail::hanshake( ChannelType& channel, const handshake_params& params, bytestring& buffer, - error_code& err + error_code& err, + error_info& info ) { + info.clear(); + // Set up processor handshake_processor processor (params); @@ -239,7 +245,7 @@ void mysql::detail::hanshake( if (err) return; // Process server greeting - err = processor.process_handshake(buffer); + err = processor.process_handshake(buffer, info); if (err) return; // Send @@ -252,7 +258,7 @@ void mysql::detail::hanshake( // Process it bool auth_complete = false; - err = processor.process_handshake_server_response(buffer, auth_complete); + err = processor.process_handshake_server_response(buffer, auth_complete, info); if (err) return; if (auth_complete) { @@ -269,14 +275,14 @@ void mysql::detail::hanshake( if (err) return; // Process it - err = processor.process_auth_switch_response(boost::asio::buffer(buffer)); + err = processor.process_auth_switch_response(boost::asio::buffer(buffer), info); if (err) return; channel.set_current_capabilities(processor.negotiated_capabilities()); } template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code)) +BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, mysql::error_info)) mysql::detail::async_handshake( ChannelType& channel, const handshake_params& params, @@ -284,7 +290,7 @@ mysql::detail::async_handshake( CompletionToken&& token ) { - using HandlerSignature = void(mysql::error_code); + using HandlerSignature = void(error_code, error_info); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using StreamType = typename ChannelType::stream_type; using BaseType = boost::beast::async_base; @@ -296,6 +302,7 @@ mysql::detail::async_handshake( ChannelType& channel_; bytestring& buffer_; handshake_processor processor_; + error_info info_; Op( HandlerType&& handler, @@ -313,7 +320,7 @@ mysql::detail::async_handshake( void complete(bool cont, error_code errc) { channel_.set_current_capabilities(processor_.negotiated_capabilities()); - BaseType::complete(cont, errc); + BaseType::complete(cont, errc, std::move(info_)); } void operator()( @@ -333,7 +340,7 @@ mysql::detail::async_handshake( } // Process server greeting - err = processor_.process_handshake(buffer_); + err = processor_.process_handshake(buffer_, info_); if (err) { complete(cont, err); @@ -357,7 +364,7 @@ mysql::detail::async_handshake( } // Process it - err = processor_.process_handshake_server_response(buffer_, auth_complete); + err = processor_.process_handshake_server_response(buffer_, auth_complete, info_); if (auth_complete) err.clear(); if (err || auth_complete) { @@ -382,7 +389,7 @@ mysql::detail::async_handshake( } // Process it - err = processor_.process_auth_switch_response(boost::asio::buffer(buffer_)); + err = processor_.process_auth_switch_response(boost::asio::buffer(buffer_), info_); if (err) { complete(cont, err); diff --git a/include/mysql/impl/messages.hpp b/include/mysql/impl/messages.hpp index 72d7de1e..02e58a3c 100644 --- a/include/mysql/impl/messages.hpp +++ b/include/mysql/impl/messages.hpp @@ -232,7 +232,7 @@ inline std::pair deserialize_message_type( DeserializationContext& ctx ); -inline error_code process_error_packet(DeserializationContext& ctx); +inline error_code process_error_packet(DeserializationContext& ctx, error_info& info); /*struct StmtPrepare diff --git a/include/mysql/impl/messages.ipp b/include/mysql/impl/messages.ipp index 6edb5cd6..eb2b659f 100644 --- a/include/mysql/impl/messages.ipp +++ b/include/mysql/impl/messages.ipp @@ -214,12 +214,14 @@ inline std::pair mysql::detail::deserialize_mes } inline mysql::error_code mysql::detail::process_error_packet( - DeserializationContext& ctx + DeserializationContext& ctx, + error_info& info ) { err_packet error_packet; auto errc = deserialize_message(error_packet, ctx); if (errc) return errc; + info.set_message(std::string(error_packet.error_message.value)); return make_error_code(static_cast(error_packet.error_code.value)); } diff --git a/include/mysql/impl/query.hpp b/include/mysql/impl/query.hpp index d007b50b..676e3139 100644 --- a/include/mysql/impl/query.hpp +++ b/include/mysql/impl/query.hpp @@ -2,6 +2,7 @@ #define MYSQL_ASIO_IMPL_QUERY_HPP #include "mysql/resultset.hpp" +#include "mysql/error.hpp" #include "mysql/impl/capabilities.hpp" #include @@ -28,11 +29,12 @@ void execute_query( ChannelType& channel, std::string_view query, channel_resultset_type& output, - error_code& err + error_code& err, + error_info& info ); template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, channel_resultset_type)) +BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, channel_resultset_type)) async_execute_query( ChannelType& channel, std::string_view query, @@ -47,11 +49,12 @@ fetch_result fetch_text_row( bytestring& buffer, std::vector& output_values, ok_packet& output_ok_packet, - error_code& err + error_code& err, + error_info& info ); template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, fetch_result)) +BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, fetch_result)) async_fetch_text_row( ChannelType& channel, const std::vector& meta, diff --git a/include/mysql/impl/query.ipp b/include/mysql/impl/query.ipp index a5020d8d..86e13506 100644 --- a/include/mysql/impl/query.ipp +++ b/include/mysql/impl/query.ipp @@ -41,7 +41,8 @@ public: std::optional // has value if there are fields in the response process_query_response( channel_resultset_type& output, - error_code& err + error_code& err, + error_info& info ) { // Response may be: ok_packet, err_packet, local infile request (not implemented) @@ -62,7 +63,7 @@ public: } else if (msg_type == error_packet_header) { - err = process_error_packet(ctx); + err = process_error_packet(ctx, info); return {}; } else @@ -117,7 +118,8 @@ inline fetch_result process_fetch_message( const bytestring& buffer, std::vector& output_values, ok_packet& output_ok_packet, - error_code& err + error_code& err, + error_info& info ) { // Message type: row, error or eof? @@ -135,7 +137,7 @@ inline fetch_result process_fetch_message( else if (msg_type == error_packet_header) { // An error occurred during the generation of the rows - err = process_error_packet(ctx); + err = process_error_packet(ctx, info); return fetch_result::error; } else @@ -156,7 +158,8 @@ void mysql::detail::execute_query( ChannelType& channel, std::string_view query, resultset>& output, - error_code& err + error_code& err, + error_info& info ) { // Compose a com_query message, reset seq num @@ -172,7 +175,7 @@ void mysql::detail::execute_query( if (err) return; // Response may be: ok_packet, err_packet, local infile request (not implemented), or response with fields - auto num_fields = processor.process_query_response(output, err); + auto num_fields = processor.process_query_response(output, err, info); if (!num_fields) // ok or err { return; @@ -199,7 +202,7 @@ void mysql::detail::execute_query( template BOOST_ASIO_INITFN_RESULT_TYPE( CompletionToken, - void(mysql::error_code, mysql::detail::channel_resultset_type) + void(mysql::error_code, mysql::error_info, mysql::detail::channel_resultset_type) ) mysql::detail::async_execute_query( ChannelType& channel, @@ -207,7 +210,7 @@ mysql::detail::async_execute_query( CompletionToken&& token ) { - using HandlerSignature = void(error_code, channel_resultset_type); + using HandlerSignature = void(error_code, error_info, channel_resultset_type); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using StreamType = typename ChannelType::stream_type; using BaseType = boost::beast::async_base; @@ -236,10 +239,11 @@ mysql::detail::async_execute_query( { ResultsetType resultset; error_code err; - auto num_fields = processor_->process_query_response(resultset, err); + error_info info; + auto num_fields = processor_->process_query_response(resultset, err, info); if (!num_fields) // ok or err { - this->complete(cont, err, std::move(resultset)); + this->complete(cont, err, std::move(info), std::move(resultset)); return false; } else @@ -253,7 +257,7 @@ mysql::detail::async_execute_query( { ResultsetType resultset; std::move(*processor_).create_resultset(resultset); - this->complete(cont, error_code(), std::move(resultset)); + this->complete(cont, error_code(), error_info(), std::move(resultset)); } void operator()( @@ -270,7 +274,7 @@ mysql::detail::async_execute_query( ); if (err) { - this->complete(cont, err, ResultsetType()); + this->complete(cont, err, error_info(), ResultsetType()); yield break; } @@ -281,7 +285,7 @@ mysql::detail::async_execute_query( ); if (err) { - this->complete(cont, err, ResultsetType()); + this->complete(cont, err, error_info(), ResultsetType()); yield break; } @@ -308,7 +312,7 @@ mysql::detail::async_execute_query( if (err) { - this->complete(cont, err, ResultsetType()); + this->complete(cont, err, error_info(), ResultsetType()); yield break; } @@ -339,7 +343,8 @@ mysql::detail::fetch_result mysql::detail::fetch_text_row( bytestring& buffer, std::vector& output_values, ok_packet& output_ok_packet, - error_code& err + error_code& err, + error_info& info ) { // Read a packet @@ -352,13 +357,17 @@ mysql::detail::fetch_result mysql::detail::fetch_text_row( buffer, output_values, output_ok_packet, - err + err, + info ); } template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, mysql::detail::fetch_result)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + void(mysql::error_code, mysql::error_info, mysql::detail::fetch_result) +) mysql::detail::async_fetch_text_row( ChannelType& channel, const std::vector& meta, @@ -401,15 +410,17 @@ mysql::detail::async_fetch_text_row( void process_result(bool cont) { error_code err; + error_info info; auto result = process_fetch_message( channel_.current_capabilities(), meta_, buffer_, output_values_, output_ok_packet_, - err + err, + info ); - this->complete(cont, err, result); + this->complete(cont, err, info, result); } void operator()( @@ -422,7 +433,7 @@ mysql::detail::async_fetch_text_row( yield channel_.async_read(buffer_, std::move(*this)); if (err) { - this->complete(cont, err, fetch_result::error); + this->complete(cont, err, error_info(), fetch_result::error); yield break; } process_result(cont); diff --git a/include/mysql/impl/resultset.hpp b/include/mysql/impl/resultset.ipp similarity index 80% rename from include/mysql/impl/resultset.hpp rename to include/mysql/impl/resultset.ipp index 861c14ec..52354bb7 100644 --- a/include/mysql/impl/resultset.hpp +++ b/include/mysql/impl/resultset.ipp @@ -9,13 +9,17 @@ template const mysql::row* mysql::resultset::fetch_one( - error_code& err + error_code& err, + error_info& info ) { assert(valid()); + + err.clear(); + info.clear(); + if (complete()) { - err.clear(); return nullptr; } auto result = detail::fetch_text_row( @@ -24,7 +28,8 @@ const mysql::row* mysql::resultset::fetch_one( buffer_, current_row_.values(), ok_packet_, - err + err, + info ); eof_received_ = result == detail::fetch_result::eof; return result == detail::fetch_result::row ? ¤t_row_ : nullptr; @@ -34,20 +39,24 @@ template const mysql::row* mysql::resultset::fetch_one() { error_code errc; - const row* res = fetch_one(errc); - detail::check_error_code(errc); + error_info info; + const row* res = fetch_one(errc, info); + detail::check_error_code(errc, info); return res; } template std::vector mysql::resultset::fetch_many( std::size_t count, - error_code& err + error_code& err, + error_info& info ) { assert(valid()); err.clear(); + info.clear(); + std::vector res; if (!complete()) // support calling fetch on already exhausted resultset @@ -63,7 +72,8 @@ std::vector mysql::resultset::fetch_many( buff, values, ok_packet_, - err + err, + info ); eof_received_ = result == detail::fetch_result::eof; if (result == detail::fetch_result::row) @@ -86,17 +96,19 @@ std::vector mysql::resultset::fetch_many( ) { error_code errc; - auto res = fetch_many(count, errc); - detail::check_error_code(errc); + error_info info; + auto res = fetch_many(count, errc, info); + detail::check_error_code(errc, info); return res; } template std::vector mysql::resultset::fetch_all( - error_code& err + error_code& err, + error_info& info ) { - return fetch_many(std::numeric_limits::max(), err); + return fetch_many(std::numeric_limits::max(), err, info); } template @@ -107,12 +119,15 @@ std::vector mysql::resultset::fetch_all() template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, const mysql::row*)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + void(mysql::error_code, mysql::error_info, const mysql::row*) +) mysql::resultset::async_fetch_one( CompletionToken&& token ) { - using HandlerSignature = void(error_code, const row*); + using HandlerSignature = void(error_code, error_info, const row*); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using BaseType = boost::beast::async_base; @@ -129,6 +144,7 @@ mysql::resultset::async_fetch_one( void operator()( error_code err, + error_info info, detail::fetch_result result, bool cont=true ) @@ -137,7 +153,7 @@ mysql::resultset::async_fetch_one( { if (resultset_.complete()) { - this->complete(cont, error_code(), nullptr); + this->complete(cont, error_code(), error_info(), nullptr); } else { @@ -153,6 +169,7 @@ mysql::resultset::async_fetch_one( this->complete( cont, err, + std::move(info), result == detail::fetch_result::row ? &resultset_.current_row_ : nullptr ); } @@ -167,19 +184,22 @@ mysql::resultset::async_fetch_one( Op( std::move(initiator.completion_handler), *this - )(error_code(), detail::fetch_result::error, false); + )(error_code(), error_info(), detail::fetch_result::error, false); return initiator.result.get(); } template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, std::vector)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + void(mysql::error_code, mysql::error_info, std::vector) +) mysql::resultset::async_fetch_many( std::size_t count, CompletionToken&& token ) { - using HandlerSignature = void(error_code, std::vector); + using HandlerSignature = void(error_code, error_info, std::vector); using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); using BaseType = boost::beast::async_base; @@ -218,6 +238,7 @@ mysql::resultset::async_fetch_many( void operator()( error_code err, + error_info info, detail::fetch_result result, bool cont=true ) @@ -236,7 +257,7 @@ mysql::resultset::async_fetch_many( ); if (result == detail::fetch_result::error) { - this->complete(cont, err, std::move(impl_->rows)); + this->complete(cont, err, std::move(info), std::move(impl_->rows)); yield break; } else if (result == detail::fetch_result::eof) @@ -248,7 +269,7 @@ mysql::resultset::async_fetch_many( impl_->row_received(); } } - this->complete(cont, err, std::move(impl_->rows)); + this->complete(cont, err, error_info(), std::move(impl_->rows)); } } }; @@ -260,13 +281,16 @@ mysql::resultset::async_fetch_many( Op( std::move(initiator.completion_handler), std::make_shared(*this, count) - )(error_code(), detail::fetch_result::error, false); + )(error_code(), error_info(), detail::fetch_result::error, false); return initiator.result.get(); } template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(mysql::error_code, std::vector)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + void(mysql::error_code, mysql::error_info, std::vector) +) mysql::resultset::async_fetch_all( CompletionToken&& token ) @@ -281,4 +305,4 @@ mysql::resultset::async_fetch_all( #include -#endif \ No newline at end of file +#endif diff --git a/include/mysql/resultset.hpp b/include/mysql/resultset.hpp index 0891384f..3ca53a8d 100644 --- a/include/mysql/resultset.hpp +++ b/include/mysql/resultset.hpp @@ -77,7 +77,7 @@ public: * fetch_one is the fetch method that performs the less memory allocations * of the three. */ - const row* fetch_one(error_code& err); + const row* fetch_one(error_code& err, error_info& info); /// Fetches a single row (sync with exceptions version). const row* fetch_one(); @@ -91,7 +91,7 @@ public: * Only if count is **greater** than the available number of rows, * the resultset will be completed. */ - std::vector fetch_many(std::size_t count, error_code& err); + std::vector fetch_many(std::size_t count, error_code& err, error_info& info); /// Fetches at most count rows (sync with exceptions version). std::vector fetch_many(std::size_t count); @@ -104,24 +104,24 @@ public: * * The resultset is guaranteed to be complete() after this call returns. */ - std::vector fetch_all(error_code& err); + std::vector fetch_all(error_code& err, error_info& info); /// Fetches all available rows (sync with exceptions version). std::vector fetch_all(); /// Fetchs a single row (async version). template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, const row*)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, const row*)) async_fetch_one(CompletionToken&& token); /// Fetches at most count rows (async version). template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, std::vector)) async_fetch_many(std::size_t count, CompletionToken&& token); /// Fetches all available rows (async version). template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, std::vector)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, error_info, std::vector)) async_fetch_all(CompletionToken&& token); /** @@ -177,6 +177,6 @@ using tcp_resultset = resultset; } -#include "mysql/impl/resultset.hpp" +#include "mysql/impl/resultset.ipp" #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 59060e85..a35a0fb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,9 +25,9 @@ add_executable( unit/row.cpp ) # A codegen issue in MSVC C++17 makes gmock expectations not work -if (NOT MSVC) - target_sources(mysql_unittests PRIVATE unit/channel.cpp) -endif() +#if (NOT MSVC) +# target_sources(mysql_unittests PRIVATE unit/channel.cpp) +#endif() target_include_directories( mysql_unittests diff --git a/test/common/test_common.hpp b/test/common/test_common.hpp index cbb457fd..5fa58b09 100644 --- a/test/common/test_common.hpp +++ b/test/common/test_common.hpp @@ -3,6 +3,7 @@ #include "mysql/value.hpp" #include "mysql/row.hpp" +#include #include namespace mysql @@ -62,6 +63,16 @@ inline std::string_view makesv(const std::uint8_t (&value) [N]) return std::string_view(reinterpret_cast(value), N); } +inline void validate_error_info(const mysql::error_info& value, const std::vector& to_check) +{ + std::string msg_lower = value.message(); + std::transform(msg_lower.begin(), msg_lower.end(), msg_lower.begin(), &tolower); + for (const auto& elm: to_check) + { + EXPECT_THAT(msg_lower, testing::HasSubstr(elm)); + } +} + } } diff --git a/test/integration/handshake.cpp b/test/integration/handshake.cpp index 23f8c866..ad99458e 100644 --- a/test/integration/handshake.cpp +++ b/test/integration/handshake.cpp @@ -7,12 +7,15 @@ #include "mysql/connection.hpp" #include "integration_test_common.hpp" +#include "test_common.hpp" #include namespace net = boost::asio; using namespace testing; +using namespace mysql::test; using mysql::detail::make_error_code; +using mysql::error_info; namespace { @@ -24,8 +27,9 @@ struct HandshakeTest : public mysql::test::IntegTest // Sync with error codes TEST_F(HandshakeTest, SyncErrc_FastAuthSuccessfulLogin) { - conn.handshake(connection_params, errc); + conn.handshake(connection_params, errc, info); EXPECT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); } // TODO: review failure in Mac @@ -40,15 +44,17 @@ TEST_F(HandshakeTest, SyncErrc_FastAuthSuccessfulLogin) TEST_F(HandshakeTest, SyncErrc_FastAuthSuccessfulLoginNoDatabase) { connection_params.database = ""; - conn.handshake(connection_params, errc); + conn.handshake(connection_params, errc, info); EXPECT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); } TEST_F(HandshakeTest, SyncErrc_FastAuthBadUser) { connection_params.username = "non_existing_user"; - conn.handshake(connection_params, errc); + conn.handshake(connection_params, errc, info); EXPECT_NE(errc, mysql::error_code()); + EXPECT_NE(info, error_info()); // TODO: if default auth plugin is unknown, unknown auth plugin is returned instead of access denied // EXPECT_EQ(errc, make_error_code(mysql::Error::access_denied_error)); } @@ -56,15 +62,17 @@ TEST_F(HandshakeTest, SyncErrc_FastAuthBadUser) TEST_F(HandshakeTest, SyncErrc_FastAuthBadPassword) { connection_params.password = "bad_password"; - conn.handshake(connection_params, errc); + conn.handshake(connection_params, errc, info); EXPECT_EQ(errc, make_error_code(mysql::Error::access_denied_error)); + validate_error_info(info, {"access denied", "integ_user"}); } TEST_F(HandshakeTest, SyncErrc_FastAuthBadDatabase) { connection_params.database = "bad_database"; - conn.handshake(connection_params, errc); + conn.handshake(connection_params, errc, info); EXPECT_EQ(errc, make_error_code(mysql::Error::bad_db_error)); + validate_error_info(info, {"unknown database", "bad_database"}); } // Sync with exceptions @@ -83,7 +91,7 @@ TEST_F(HandshakeTest, SyncExc_FastAuthBadPassword) TEST_F(HandshakeTest, Async_FastAuthSuccessfulLogin) { auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - EXPECT_NO_THROW(fut.get()); + EXPECT_EQ(fut.get(), error_info()); } // TODO: review failure in Mac @@ -92,21 +100,20 @@ TEST_F(HandshakeTest, Async_FastAuthSuccessfulLogin) connection_params.username = "empty_password_user"; connection_params.password = ""; auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - EXPECT_NO_THROW(fut.get()); + EXPECT_EQ(fut.get(), error_info()); }*/ TEST_F(HandshakeTest, Async_FastAuthSuccessfulLoginNoDatabase) { connection_params.database = ""; auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - EXPECT_NO_THROW(fut.get()); + EXPECT_EQ(fut.get(), error_info()); } TEST_F(HandshakeTest, Async_FastAuthBadUser) { connection_params.username = "non_existing_user"; auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - EXPECT_THROW(fut.get(), boost::system::system_error); // TODO: if default auth plugin is unknown, unknown auth plugin is returned instead of access denied // validate_future_exception(fut, make_error_code(mysql::Error::access_denied_error)); @@ -115,15 +122,17 @@ TEST_F(HandshakeTest, Async_FastAuthBadUser) TEST_F(HandshakeTest, Async_FastAuthBadPassword) { connection_params.password = "bad_password"; - auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - validate_future_exception(fut, make_error_code(mysql::Error::access_denied_error)); + validate_async_fail([&](auto&& cb) { + conn.async_handshake(connection_params, std::move(cb)); + }, mysql::Error::access_denied_error, {"access denied", "integ_user"}); } TEST_F(HandshakeTest, Async_FastAuthBadDatabase) { connection_params.database = "bad_db"; - auto fut = conn.async_handshake(connection_params, boost::asio::use_future); - validate_future_exception(fut, make_error_code(mysql::Error::bad_db_error)); + validate_async_fail([&](auto&& cb) { + conn.async_handshake(connection_params, std::move(cb)); + }, mysql::Error::bad_db_error, {"unknown database", "bad_db"}); } } // anon namespace diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index 6df2744b..b8b8c190 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "test_common.hpp" namespace mysql { @@ -19,6 +20,7 @@ struct IntegTest : testing::Test boost::asio::io_context ctx; mysql::connection conn {ctx}; mysql::error_code errc; + mysql::error_info info; boost::asio::executor_work_guard guard { ctx.get_executor() }; std::thread runner {[this]{ ctx.run(); } }; @@ -26,6 +28,7 @@ struct IntegTest : testing::Test { boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306); conn.next_level().connect(endpoint); + reset_errors(); } ~IntegTest() @@ -35,24 +38,46 @@ struct IntegTest : testing::Test runner.join(); } - template - void validate_future_exception(std::future& fut, mysql::error_code expected_errc) + template + void validate_async_fail( + Callable&& initiator, + error_code expected_errc, + const std::vector& expected_info + ) { - try - { - fut.get(); - FAIL() << "Expected asynchronous operation to fail"; - } - catch (const boost::system::system_error& exc) - { - EXPECT_EQ(exc.code(), expected_errc); - } + std::promise> prom; + initiator([&prom](error_code errc, error_info info, auto&&...) { + prom.set_value(std::make_pair(errc, std::move(info))); + }); + auto [actual_errc, actual_info] = prom.get_future().get(); + EXPECT_EQ(actual_errc, expected_errc); + validate_error_info(actual_info, expected_info); + } + + template + void validate_async_fail( + Callable&& initiator, + Error expected_errc, + const std::vector& expected_info + ) + { + validate_async_fail( + std::forward(initiator), + ::mysql::detail::make_error_code(expected_errc), + expected_info + ); } void handshake() { conn.handshake(connection_params); } + + void reset_errors() + { + // TODO: set errc to something not null to verify we clear stuff + info.set_message("Previous error message was not cleared correctly"); + } }; } diff --git a/test/integration/query.cpp b/test/integration/query.cpp index a94351a3..245d2dcd 100644 --- a/test/integration/query.cpp +++ b/test/integration/query.cpp @@ -21,6 +21,7 @@ using mysql::test::validate_meta; using mysql::field_metadata; using mysql::field_type; using mysql::error_code; +using mysql::error_info; namespace { @@ -68,14 +69,22 @@ struct QueryTest : public mysql::test::IntegTest { validate_2fields_meta(result.fields(), table); } + + auto make_query_initiator(const char* sql) + { + return [this, sql](auto&& cb) { + conn.async_query(sql, cb); + }; + } }; // Query, sync errc TEST_F(QueryTest, QuerySyncErrc_InsertQueryOk) { - auto result = conn.query( - "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", errc); - ASSERT_EQ(errc, mysql::error_code()); + const char* sql = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; + auto result = conn.query(sql, errc, info); + ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.fields().empty()); EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.complete()); @@ -87,17 +96,19 @@ TEST_F(QueryTest, QuerySyncErrc_InsertQueryOk) TEST_F(QueryTest, QuerySyncErrc_InsertQueryFailed) { - auto result = conn.query( - "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", errc); + const char* sql = "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"; + auto result = conn.query(sql, errc, info); ASSERT_EQ(errc, make_error_code(mysql::Error::no_such_table)); + validate_error_info(info, {"table", "doesn't exist", "bad_table"}); EXPECT_FALSE(result.valid()); } TEST_F(QueryTest, QuerySyncErrc_UpdateQueryOk) { - auto result = conn.query( - "UPDATE updates_table SET field_int = field_int+1", errc); + const char* sql = "UPDATE updates_table SET field_int = field_int+1"; + auto result = conn.query(sql, errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.fields().empty()); EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.complete()); @@ -109,8 +120,9 @@ TEST_F(QueryTest, QuerySyncErrc_UpdateQueryOk) TEST_F(QueryTest, QuerySyncErrc_SelectOk) { - auto result = conn.query("SELECT * FROM empty_table", errc); + auto result = conn.query("SELECT * FROM empty_table", errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.valid()); EXPECT_FALSE(result.complete()); validate_2fields_meta(result, "empty_table"); @@ -118,8 +130,9 @@ TEST_F(QueryTest, QuerySyncErrc_SelectOk) TEST_F(QueryTest, QuerySyncErrc_SelectQueryFailed) { - auto result = conn.query("SELECT field_varchar, field_bad FROM one_row_table", errc); + auto result = conn.query("SELECT field_varchar, field_bad FROM one_row_table", errc, info); ASSERT_EQ(errc, make_error_code(mysql::Error::bad_field_error)); + validate_error_info(info, {"unknown column", "field_bad"}); EXPECT_FALSE(result.valid()); } @@ -148,10 +161,11 @@ TEST_F(QueryTest, QuerySyncExc_Error) // Query, async TEST_F(QueryTest, QueryAsync_InsertQueryOk) { - auto result = conn.async_query( + auto [info, result] = conn.async_query( "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", net::use_future ).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.fields().empty()); EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.complete()); @@ -163,19 +177,20 @@ TEST_F(QueryTest, QueryAsync_InsertQueryOk) TEST_F(QueryTest, QueryAsync_InsertQueryFailed) { - auto fut = conn.async_query( - "INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", - net::use_future + validate_async_fail( + make_query_initiator("INSERT INTO bad_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')"), + mysql::Error::no_such_table, + {"table", "doesn't exist", "bad_table"} ); - validate_future_exception(fut, make_error_code(mysql::Error::no_such_table)); } TEST_F(QueryTest, QueryAsync_UpdateQueryOk) { - auto result = conn.async_query( + auto [info, result] = conn.async_query( "UPDATE updates_table SET field_int = field_int+1", net::use_future ).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.fields().empty()); EXPECT_TRUE(result.valid()); EXPECT_TRUE(result.complete()); @@ -187,7 +202,8 @@ TEST_F(QueryTest, QueryAsync_UpdateQueryOk) TEST_F(QueryTest, QueryAsync_SelectOk) { - auto result = conn.async_query("SELECT * FROM empty_table", net::use_future).get(); + auto [info, result] = conn.async_query("SELECT * FROM empty_table", net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.valid()); EXPECT_FALSE(result.complete()); validate_2fields_meta(result, "empty_table"); @@ -195,8 +211,11 @@ TEST_F(QueryTest, QueryAsync_SelectOk) TEST_F(QueryTest, QueryAsync_SelectQueryFailed) { - auto fut = conn.async_query("SELECT field_varchar, field_bad FROM one_row_table", net::use_future); - validate_future_exception(fut, make_error_code(mysql::Error::bad_field_error)); + validate_async_fail( + make_query_initiator("SELECT field_varchar, field_bad FROM one_row_table"), + mysql::Error::bad_field_error, + {"unknown column", "field_bad"} + ); } @@ -209,14 +228,17 @@ TEST_F(QueryTest, FetchOneSyncErrc_NoResults) EXPECT_EQ(result.fields().size(), 2); // Already in the end of the resultset, we receive the EOF - const mysql::row* row = result.fetch_one(errc); + const mysql::row* row = result.fetch_one(errc, info); EXPECT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); EXPECT_EQ(row, nullptr); validate_eof(result); // Fetching again just returns null - row = result.fetch_one(errc); + reset_errors(); + row = result.fetch_one(errc, info); EXPECT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); EXPECT_EQ(row, nullptr); validate_eof(result); } @@ -229,16 +251,19 @@ TEST_F(QueryTest, FetchOneSyncErrc_OneRow) EXPECT_EQ(result.fields().size(), 2); // Fetch only row - const mysql::row* row = result.fetch_one(errc); + const mysql::row* row = result.fetch_one(errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); validate_2fields_meta(result, "one_row_table"); EXPECT_EQ(row->values(), makevalues(1, "f0")); EXPECT_FALSE(result.complete()); // Fetch next: end of resultset - row = result.fetch_one(errc); + reset_errors(); + row = result.fetch_one(errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); ASSERT_EQ(row, nullptr); validate_eof(result); } @@ -251,24 +276,29 @@ TEST_F(QueryTest, FetchOneSyncErrc_TwoRows) EXPECT_EQ(result.fields().size(), 2); // Fetch first row - const mysql::row* row = result.fetch_one(errc); + const mysql::row* row = result.fetch_one(errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); validate_2fields_meta(result, "two_rows_table"); EXPECT_EQ(row->values(), makevalues(1, "f0")); EXPECT_FALSE(result.complete()); // Fetch next row - row = result.fetch_one(errc); + reset_errors(); + row = result.fetch_one(errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); validate_2fields_meta(result, "two_rows_table"); EXPECT_EQ(row->values(), makevalues(2, "f1")); EXPECT_FALSE(result.complete()); // Fetch next: end of resultset - row = result.fetch_one(errc); + reset_errors(); + row = result.fetch_one(errc, info); ASSERT_EQ(errc, mysql::error_code()); + EXPECT_EQ(info, error_info()); ASSERT_EQ(row, nullptr); validate_eof(result); } @@ -309,12 +339,14 @@ TEST_F(QueryTest, FetchOneAsync_NoResults) auto result = conn.query("SELECT * FROM empty_table"); // Already in the end of the resultset, we receive the EOF - const auto* row = result.async_fetch_one(net::use_future).get(); + auto [info, row] = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_EQ(row, nullptr); validate_eof(result); // Fetching again just returns null - row = result.async_fetch_one(net::use_future).get(); + std::tie(info, row) = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_EQ(row, nullptr); validate_eof(result); } @@ -324,13 +356,16 @@ TEST_F(QueryTest, FetchOneAsync_OneRow) auto result = conn.query("SELECT * FROM one_row_table"); // Fetch only row - const auto* row = result.async_fetch_one(net::use_future).get(); + auto [info, row] = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); EXPECT_EQ(row->values(), makevalues(1, "f0")); EXPECT_FALSE(result.complete()); // Fetch next: end of resultset - row = result.async_fetch_one(net::use_future).get(); + reset_errors(); + std::tie(info, row) = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); ASSERT_EQ(row, nullptr); validate_eof(result); } @@ -340,19 +375,24 @@ TEST_F(QueryTest, FetchOneAsync_TwoRows) auto result = conn.query("SELECT * FROM two_rows_table"); // Fetch first row - const auto* row = result.async_fetch_one(net::use_future).get(); + auto [info, row] = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); EXPECT_EQ(row->values(), makevalues(1, "f0")); EXPECT_FALSE(result.complete()); // Fetch next row - row = result.async_fetch_one(net::use_future).get(); + reset_errors(); + std::tie(info, row) = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); ASSERT_NE(row, nullptr); EXPECT_EQ(row->values(), makevalues(2, "f1")); EXPECT_FALSE(result.complete()); // Fetch next: end of resultset - row = result.async_fetch_one(net::use_future).get(); + reset_errors(); + std::tie(info, row) = result.async_fetch_one(net::use_future).get(); + EXPECT_EQ(info, error_info()); ASSERT_EQ(row, nullptr); validate_eof(result); } @@ -363,15 +403,18 @@ TEST_F(QueryTest, FetchManySyncErrc_NoResults) auto result = conn.query("SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows = result.fetch_many(10, errc); + auto rows = result.fetch_many(10, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); validate_eof(result); // Fetch again, should return OK and empty - rows = result.fetch_many(10, errc); + reset_errors(); + rows = result.fetch_many(10, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); validate_eof(result); @@ -382,14 +425,17 @@ TEST_F(QueryTest, FetchManySyncErrc_MoreRowsThanCount) auto result = conn.query("SELECT * FROM three_rows_table"); // Fetch 2, one remaining - auto rows = result.fetch_many(2, errc); + auto rows = result.fetch_many(2, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); // Fetch another two (completes the resultset) - rows = result.fetch_many(2, errc); + reset_errors(); + rows = result.fetch_many(2, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.complete()); validate_eof(result); EXPECT_EQ(rows, (makerows(2, 3, "f2"))); @@ -400,8 +446,9 @@ TEST_F(QueryTest, FetchManySyncErrc_LessRowsThanCount) auto result = conn.query("SELECT * FROM two_rows_table"); // Fetch 3, resultset exhausted - auto rows = result.fetch_many(3, errc); + auto rows = result.fetch_many(3, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); validate_eof(result); } @@ -411,14 +458,17 @@ TEST_F(QueryTest, FetchManySyncErrc_SameRowsAsCount) auto result = conn.query("SELECT * FROM two_rows_table"); // Fetch 2, 0 remaining but resultset not exhausted - auto rows = result.fetch_many(2, errc); + auto rows = result.fetch_many(2, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); // Fetch again, exhausts the resultset - rows = result.fetch_many(2, errc); + reset_errors(); + rows = result.fetch_many(2, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_EQ(rows.size(), 0); validate_eof(result); } @@ -428,8 +478,9 @@ TEST_F(QueryTest, FetchManySyncErrc_CountEqualsOne) auto result = conn.query("SELECT * FROM one_row_table"); // Fetch 1, 1 remaining - auto rows = result.fetch_many(1, errc); + auto rows = result.fetch_many(1, errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0"))); } @@ -460,13 +511,16 @@ TEST_F(QueryTest, FetchManyAsync_NoResults) auto result = conn.query("SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows = result.async_fetch_many(10, net::use_future).get(); + auto [info, rows] = result.async_fetch_many(10, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); validate_eof(result); // Fetch again, should return OK and empty - rows = result.async_fetch_many(10, net::use_future).get(); + reset_errors(); + std::tie(info, rows) = result.async_fetch_many(10, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); validate_eof(result); @@ -477,12 +531,14 @@ TEST_F(QueryTest, FetchManyAsync_MoreRowsThanCount) auto result = conn.query("SELECT * FROM three_rows_table"); // Fetch 2, one remaining - auto rows = result.async_fetch_many(2, net::use_future).get(); + auto [info, rows] = result.async_fetch_many(2, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); // Fetch another two (completes the resultset) - rows = result.async_fetch_many(2, net::use_future).get(); + std::tie(info, rows) = result.async_fetch_many(2, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.complete()); validate_eof(result); EXPECT_EQ(rows, (makerows(2, 3, "f2"))); @@ -493,7 +549,8 @@ TEST_F(QueryTest, FetchManyAsync_LessRowsThanCount) auto result = conn.query("SELECT * FROM two_rows_table"); // Fetch 3, resultset exhausted - auto rows = result.async_fetch_many(3, net::use_future).get(); + auto [info, rows] = result.async_fetch_many(3, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); validate_eof(result); } @@ -503,12 +560,15 @@ TEST_F(QueryTest, FetchManyAsync_SameRowsAsCount) auto result = conn.query("SELECT * FROM two_rows_table"); // Fetch 2, 0 remaining but resultset not exhausted - auto rows = result.async_fetch_many(2, net::use_future).get(); + auto [info, rows] = result.async_fetch_many(2, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); // Fetch again, exhausts the resultset - rows = result.async_fetch_many(2, net::use_future).get(); + reset_errors(); + std::tie(info, rows) = result.async_fetch_many(2, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_EQ(rows.size(), 0); validate_eof(result); } @@ -518,7 +578,8 @@ TEST_F(QueryTest, FetchManyAsync_CountEqualsOne) auto result = conn.query("SELECT * FROM one_row_table"); // Fetch 1, 1 remaining - auto rows = result.async_fetch_many(1, net::use_future).get(); + auto [info, rows] = result.async_fetch_many(1, net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_FALSE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0"))); } @@ -529,14 +590,17 @@ TEST_F(QueryTest, FetchAllSyncErrc_NoResults) auto result = conn.query("SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows = result.fetch_all(errc); + auto rows = result.fetch_all(errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); // Fetch again, should return OK and empty - rows = result.fetch_all(errc); + reset_errors(); + rows = result.fetch_all(errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); validate_eof(result); } @@ -545,8 +609,9 @@ TEST_F(QueryTest, FetchAllSyncErrc_OneRow) { auto result = conn.query("SELECT * FROM one_row_table"); - auto rows = result.fetch_all(errc); + auto rows = result.fetch_all(errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0"))); } @@ -555,8 +620,9 @@ TEST_F(QueryTest, FetchAllSyncErrc_SeveralRows) { auto result = conn.query("SELECT * FROM two_rows_table"); - auto rows = result.fetch_all(errc); + auto rows = result.fetch_all(errc, info); ASSERT_EQ(errc, error_code()); + EXPECT_EQ(info, error_info()); validate_eof(result); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); } @@ -578,12 +644,15 @@ TEST_F(QueryTest, FetchAllAsync_NoResults) auto result = conn.query("SELECT * FROM empty_table"); // Fetch many, but there are no results - auto rows = result.async_fetch_all(net::use_future).get(); + auto [info, rows] = result.async_fetch_all(net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); EXPECT_TRUE(result.complete()); // Fetch again, should return OK and empty - rows = result.async_fetch_all(net::use_future).get(); + reset_errors(); + std::tie(info, rows) = result.async_fetch_all(net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(rows.empty()); validate_eof(result); } @@ -592,7 +661,8 @@ TEST_F(QueryTest, FetchAllAsync_OneRow) { auto result = conn.query("SELECT * FROM one_row_table"); - auto rows = result.async_fetch_all(net::use_future).get(); + auto [info, rows] = result.async_fetch_all(net::use_future).get(); + EXPECT_EQ(info, error_info()); EXPECT_TRUE(result.complete()); EXPECT_EQ(rows, (makerows(2, 1, "f0"))); } @@ -601,7 +671,8 @@ TEST_F(QueryTest, FetchAllAsync_SeveralRows) { auto result = conn.query("SELECT * FROM two_rows_table"); - auto rows = result.async_fetch_all(net::use_future).get(); + auto [info, rows] = result.async_fetch_all(net::use_future).get(); + EXPECT_EQ(info, error_info()); validate_eof(result); EXPECT_EQ(rows, (makerows(2, 1, "f0", 2, "f1"))); } @@ -616,8 +687,3 @@ TEST_F(QueryTest, QueryAndFetch_AliasedTableAndField_MetadataCorrect) } } // anon namespace - - - - - diff --git a/test/integration/run_tests.sh b/test/integration/run_tests.sh index 4fa35300..f8e77015 100755 --- a/test/integration/run_tests.sh +++ b/test/integration/run_tests.sh @@ -4,5 +4,9 @@ set -e SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" -mysql -u root < $SCRIPTPATH/db_setup.sql +if [ "$MYSQL_SKIP_DB_SETUP" != "1" ] +then + mysql -u root < $SCRIPTPATH/db_setup.sql +fi + ./mysql_integrationtests \ No newline at end of file