From 09f7bd9aed04a3ad0bce521ab2f33d4df5bf650f Mon Sep 17 00:00:00 2001 From: ruben Date: Sat, 28 Mar 2020 16:06:36 +0000 Subject: [PATCH] Added coroutines to integration tests Changed all prepared_statement functions to not use auto return type declaration Fixed wrong return value in prepared_statement::execute --- TODO.txt | 1 + examples/metadata.cpp | 2 +- examples/prepared_statements.cpp | 2 +- examples/query_async_callbacks.cpp | 2 +- examples/query_async_coroutines.cpp | 2 +- examples/query_sync.cpp | 2 +- include/boost/mysql/connection.hpp | 10 +- .../boost/mysql/impl/prepared_statement.hpp | 16 ++- include/boost/mysql/prepared_statement.hpp | 21 ++- include/boost/mysql/resultset.hpp | 6 + test/CMakeLists.txt | 3 + test/integration/integration_test_common.hpp | 4 +- test/integration/network_functions.cpp | 121 +++++++++++++++++- test/integration/network_functions.hpp | 6 +- 14 files changed, 174 insertions(+), 24 deletions(-) diff --git a/TODO.txt b/TODO.txt index fe1c0253..a232bf12 100644 --- a/TODO.txt +++ b/TODO.txt @@ -15,6 +15,7 @@ Handshake SSL compression Usability + Static asserts for completion tokens Connection quit Incomplete query reads: how does this affect further queries? Metadata in rows: being able to index by name diff --git a/examples/metadata.cpp b/examples/metadata.cpp index 736424a0..cfdfcb42 100644 --- a/examples/metadata.cpp +++ b/examples/metadata.cpp @@ -31,7 +31,7 @@ void main_impl(int argc, char** argv) // TCP and MySQL level connect boost::asio::io_context ctx; boost::mysql::tcp_connection conn (ctx); - conn.next_level().connect(ep); + conn.next_layer().connect(ep); conn.handshake(params); // Issue the query diff --git a/examples/prepared_statements.cpp b/examples/prepared_statements.cpp index 66de5bd1..0c49206f 100644 --- a/examples/prepared_statements.cpp +++ b/examples/prepared_statements.cpp @@ -39,7 +39,7 @@ void main_impl(int argc, char** argv) // Declare the connection object and authenticate to the server boost::mysql::tcp_connection conn (ctx); - conn.next_level().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket + conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket conn.handshake(params); // Authenticates to the MySQL server /** diff --git a/examples/query_async_callbacks.cpp b/examples/query_async_callbacks.cpp index 76797dbb..4da8ed2b 100644 --- a/examples/query_async_callbacks.cpp +++ b/examples/query_async_callbacks.cpp @@ -64,7 +64,7 @@ public: void connect() { - connection.next_level().async_connect(ep, [this](error_code err) { + connection.next_layer().async_connect(ep, [this](error_code err) { die_on_error(err); connection.async_handshake(conn_params, [this](error_code err, const error_info& info) { die_on_error(err, info); diff --git a/examples/query_async_coroutines.cpp b/examples/query_async_coroutines.cpp index 7615b807..68a848dd 100644 --- a/examples/query_async_coroutines.cpp +++ b/examples/query_async_coroutines.cpp @@ -68,7 +68,7 @@ void main_impl(int argc, char** argv) boost::mysql::error_code ec; // TCP connect - conn.next_level().async_connect(ep, yield[ec]); + conn.next_layer().async_connect(ep, yield[ec]); check_error(ec); // MySQL handshake diff --git a/examples/query_sync.cpp b/examples/query_sync.cpp index 4d902396..025cedcd 100644 --- a/examples/query_sync.cpp +++ b/examples/query_sync.cpp @@ -65,7 +65,7 @@ void main_impl(int argc, char** argv) * - Authenticating to the MySQL server. */ boost::mysql::tcp_connection conn (ctx); - conn.next_level().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket + conn.next_layer().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket conn.handshake(params); // Authenticates to the MySQL server /** diff --git a/include/boost/mysql/connection.hpp b/include/boost/mysql/connection.hpp index 7794eecc..bfb2b627 100644 --- a/include/boost/mysql/connection.hpp +++ b/include/boost/mysql/connection.hpp @@ -67,7 +67,7 @@ class connection { using channel_type = detail::channel; - Stream next_level_; + Stream next_layer_; channel_type channel_; public: /** @@ -76,16 +76,16 @@ public: */ template connection(Args&&... args) : - next_level_(std::forward(args)...), - channel_(next_level_) + next_layer_(std::forward(args)...), + channel_(next_layer_) { } /// Retrieves the underlying Stream object. - Stream& next_level() { return next_level_; } + Stream& next_layer() { return next_layer_; } /// Retrieves the underlying Stream object. - const Stream& next_level() const { return next_level_; } + const Stream& next_layer() const { return next_layer_; } /// Performs the MySQL-level handshake (synchronous with error code version). void handshake(const connection_params& params, error_code& ec, error_info& info); diff --git a/include/boost/mysql/impl/prepared_statement.hpp b/include/boost/mysql/impl/prepared_statement.hpp index dd762270..84b9f37f 100644 --- a/include/boost/mysql/impl/prepared_statement.hpp +++ b/include/boost/mysql/impl/prepared_statement.hpp @@ -74,7 +74,11 @@ boost::mysql::resultset boost::mysql::prepared_statement::execut template template -auto boost::mysql::prepared_statement::async_execute( +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + typename boost::mysql::prepared_statement::execute_signature +) +boost::mysql::prepared_statement::async_execute( ForwardIterator params_first, ForwardIterator params_last, CompletionToken&& token @@ -89,7 +93,8 @@ auto boost::mysql::prepared_statement::async_execute( using HandlerArg = async_handler_arg>; using HandlerSignature = void(error_code, HandlerArg); boost::asio::async_completion completion (token); - return boost::asio::post( + // TODO: is executor correctly preserved here? + boost::asio::post( channel_->next_layer().get_executor(), boost::beast::bind_front_handler( std::move(completion.completion_handler), @@ -97,6 +102,7 @@ auto boost::mysql::prepared_statement::async_execute( HandlerArg(std::move(info)) ) ); + return completion.result.get(); } // Actually execute the statement @@ -133,7 +139,11 @@ void boost::mysql::prepared_statement::close() template template -auto boost::mysql::prepared_statement::async_close( +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + typename boost::mysql::prepared_statement::close_signature +) +boost::mysql::prepared_statement::async_close( CompletionToken&& token ) { diff --git a/include/boost/mysql/prepared_statement.hpp b/include/boost/mysql/prepared_statement.hpp index ef203e29..dc41e6be 100644 --- a/include/boost/mysql/prepared_statement.hpp +++ b/include/boost/mysql/prepared_statement.hpp @@ -59,6 +59,12 @@ public: prepared_statement(detail::channel& chan, const detail::com_stmt_prepare_ok_packet& msg) noexcept: channel_(&chan), stmt_msg_(msg) {} + /// Retrieves the stream object associated with the underlying connection. + Stream& next_layer() noexcept { assert(channel_); return channel_->next_layer(); } + + /// Retrieves the stream object associated with the underlying connection. + const Stream& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); } + /// Returns true if the statement is not a default-constructed object. bool valid() const noexcept { return channel_ != nullptr; } @@ -86,13 +92,17 @@ public: return execute(std::begin(params), std::end(params)); } + /// The handler signature for execute. + using execute_signature = void(error_code, async_handler_arg>); + /** * \brief Executes a statement (collection, sync with exceptions code version). * \details It is **not** necessary to keep the collection of parameters or the * values they may point to alive. */ template - auto async_execute(const Collection& params, CompletionToken&& token) const + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_signature) + async_execute(const Collection& params, CompletionToken&& token) const { return async_execute(std::begin(params), std::end(params), std::forward(token)); } @@ -118,7 +128,8 @@ public: * by the elements of the sequence need **not** be kept alive. */ template - auto async_execute(ForwardIterator params_first, ForwardIterator params_last, CompletionToken&& token) const; + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, execute_signature) + async_execute(ForwardIterator params_first, ForwardIterator params_last, CompletionToken&& token) const; /** * \brief Closes a prepared statement, deallocating it from the server (sync with error code version). @@ -137,9 +148,13 @@ public: /// Closes a prepared statement, deallocating it from the server (sync with exceptions version). void close(); + /// The handler signature for close. + using close_signature = void(error_code, error_info); + /// Closes a prepared statement, deallocating it from the server (async version). template - auto async_close(CompletionToken&& token); + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, close_signature) + async_close(CompletionToken&& token); }; /// A prepared statement associated to a TCP connection to the MySQL server. diff --git a/include/boost/mysql/resultset.hpp b/include/boost/mysql/resultset.hpp index 3297d442..ad2c0b94 100644 --- a/include/boost/mysql/resultset.hpp +++ b/include/boost/mysql/resultset.hpp @@ -70,6 +70,12 @@ public: resultset(channel_type& channel, detail::bytestring&& buffer, const detail::ok_packet& ok_pack): channel_(&channel), buffer_(std::move(buffer)), ok_packet_(ok_pack), eof_received_(true) {}; + /// Retrieves the stream object associated with the underlying connection. + StreamType& next_layer() noexcept { assert(channel_); return channel_->next_layer(); } + + /// Retrieves the stream object associated with the underlying connection. + const StreamType& next_layer() const noexcept { assert(channel_); return channel_->next_layer(); } + /** * \brief Fetches a single row (sync with error code version). * \details The returned object will be nullptr if there are no more rows diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 10b2595a..e7a5bc23 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,6 +58,8 @@ add_test( ) # Integration testing +find_package(Boost REQUIRED COMPONENTS coroutine) + add_executable( mysql_integrationtests integration/metadata_validator.cpp @@ -78,6 +80,7 @@ target_link_libraries( gtest_main gmock mysql_asio + Boost::coroutine ) target_include_directories( mysql_integrationtests diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index 416dccbe..38514667 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -29,7 +29,7 @@ struct IntegTest : testing::Test try { boost::asio::ip::tcp::endpoint endpoint (boost::asio::ip::address_v4::loopback(), 3306); - conn.next_level().connect(endpoint); + conn.next_layer().connect(endpoint); } catch (...) // prevent terminate without an active exception on connect error { @@ -42,7 +42,7 @@ struct IntegTest : testing::Test ~IntegTest() { error_code code; - conn.next_level().close(code); + conn.next_layer().close(code); guard.reset(); runner.join(); } diff --git a/test/integration/network_functions.cpp b/test/integration/network_functions.cpp index 1f5315bc..8915d784 100644 --- a/test/integration/network_functions.cpp +++ b/test/integration/network_functions.cpp @@ -1,4 +1,5 @@ #include "network_functions.hpp" +#include #include using namespace boost::mysql::test; @@ -12,6 +13,7 @@ using boost::mysql::errc; using boost::mysql::value; using boost::mysql::row; using boost::mysql::owning_row; +using boost::asio::yield_context; namespace { @@ -214,7 +216,7 @@ public: } }; -class async : public network_functions +class async_callback : public network_functions { template static network_result impl(Callable&& cb) { @@ -238,7 +240,7 @@ class async : public network_functions return prom.get_future().get(); } public: - const char* name() const override { return "async"; } + const char* name() const override { return "async_callback"; } network_result handshake( tcp_connection& conn, const boost::mysql::connection_params& params @@ -320,14 +322,125 @@ public: } }; +class async_coroutine : public network_functions +{ + template + static auto impl(IoObj& obj, Callable&& cb) { + using R = typename decltype(cb(std::declval()))::value_type; + auto executor = obj.next_layer().get_executor(); + std::promise> prom; + boost::asio::spawn(executor, [&](yield_context yield) { + error_code ec; + auto result = cb(yield[ec]); + prom.set_value(network_result{ec, std::move(result.error()), std::move(result.get())}); + }); + return prom.get_future().get(); + } + + template + static network_result impl_no_result(IoObj& obj, Callable&& cb) { + auto executor = obj.next_layer().get_executor(); + std::promise> prom; + boost::asio::spawn(executor, [&](yield_context yield) { + error_code ec; + error_info info = cb(yield[ec]); + prom.set_value(network_result{ec, std::move(info), no_result()}); + }); + return prom.get_future().get(); + } +public: + const char* name() const override { return "async_coroutine"; } + network_result handshake( + tcp_connection& conn, + const boost::mysql::connection_params& params + ) override + { + return impl_no_result(conn, [&](yield_context yield) { + return conn.async_handshake(params, yield); + }); + } + network_result query( + tcp_connection& conn, + std::string_view query + ) override + { + return impl(conn, [&](yield_context yield) { + return conn.async_query(query, yield); + }); + } + network_result prepare_statement( + tcp_connection& conn, + std::string_view statement + ) override + { + return impl(conn, [&](yield_context yield) { + return conn.async_prepare_statement(statement, yield); + }); + } + network_result execute_statement( + tcp_prepared_statement& stmt, + value_list_it params_first, + value_list_it params_last + ) override + { + return impl(stmt, [&](yield_context yield) { + return stmt.async_execute(params_first, params_last, yield); + }); + } + network_result execute_statement( + tcp_prepared_statement& stmt, + const std::vector& values + ) override + { + return impl(stmt, [&](yield_context yield) { + return stmt.async_execute(values, yield); + }); + } + network_result close_statement( + tcp_prepared_statement& stmt + ) override + { + return impl_no_result(stmt, [&](yield_context yield) { + return stmt.async_close(yield); + }); + } + network_result fetch_one( + tcp_resultset& r + ) override + { + return impl(r, [&](yield_context yield) { + return r.async_fetch_one(yield); + }); + } + network_result> fetch_many( + tcp_resultset& r, + std::size_t count + ) override + { + return impl(r, [&](yield_context yield) { + return r.async_fetch_many(count, yield); + }); + } + network_result> fetch_all( + tcp_resultset& r + ) override + { + return impl(r, [&](yield_context yield) { + return r.async_fetch_all(yield); + }); + } +}; + // Global objects to be exposed sync_errc sync_errc_obj; sync_exc sync_exc_obj; -async async_obj; +async_callback async_callback_obj; +async_coroutine async_coroutine_obj; } // Visible stuff boost::mysql::test::network_functions* boost::mysql::test::sync_errc_network_functions = &sync_errc_obj; boost::mysql::test::network_functions* boost::mysql::test::sync_exc_network_functions = &sync_exc_obj; -boost::mysql::test::network_functions* boost::mysql::test::async_network_functions = &async_obj; +boost::mysql::test::network_functions* boost::mysql::test::async_callback_network_functions = &async_callback_obj; +boost::mysql::test::network_functions* boost::mysql::test::async_coroutine_network_functions = &async_coroutine_obj; diff --git a/test/integration/network_functions.hpp b/test/integration/network_functions.hpp index a49b19a1..0464425a 100644 --- a/test/integration/network_functions.hpp +++ b/test/integration/network_functions.hpp @@ -66,12 +66,14 @@ public: extern network_functions* sync_errc_network_functions; extern network_functions* sync_exc_network_functions; -extern network_functions* async_network_functions; +extern network_functions* async_callback_network_functions; +extern network_functions* async_coroutine_network_functions; inline network_functions* all_network_functions [] = { sync_errc_network_functions, sync_exc_network_functions, - async_network_functions + async_callback_network_functions, + async_coroutine_network_functions }; #define MYSQL_NETWORK_TEST_SUITE(TestSuiteName) \