From e0f83bf1c154eb000f5b076efc4d1f35c300dcf8 Mon Sep 17 00:00:00 2001 From: ruben Date: Wed, 5 Feb 2020 12:44:33 +0000 Subject: [PATCH] Added prepared_statement::execute tests Fixed bug in prepare_statement (was not reading metadata packets after the prepared statement response) Added stringize() Now execute() explicitly checks the number of parameters and issues an error if they don't match the expected count --- include/mysql/error.hpp | 3 +- include/mysql/impl/error.hpp | 1 + .../network_algorithms/prepare_statement.ipp | 29 +++++++--- include/mysql/impl/prepared_statement.ipp | 33 ++++++++--- include/mysql/impl/stringize.hpp | 25 +++++++++ test/CMakeLists.txt | 1 + test/integration/execute_statement.cpp | 56 +++++++++++++++++++ 7 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 include/mysql/impl/stringize.hpp create mode 100644 test/integration/execute_statement.cpp diff --git a/include/mysql/error.hpp b/include/mysql/error.hpp index 4d65018a..7fd47bea 100644 --- a/include/mysql/error.hpp +++ b/include/mysql/error.hpp @@ -22,7 +22,8 @@ enum class Error : int sequence_number_mismatch, server_unsupported, protocol_value_error, - unknown_auth_plugin + unknown_auth_plugin, + wrong_num_params }; /// An alias for boost::system error codes. diff --git a/include/mysql/impl/error.hpp b/include/mysql/impl/error.hpp index e6db3008..33bd3a6e 100644 --- a/include/mysql/impl/error.hpp +++ b/include/mysql/impl/error.hpp @@ -35,6 +35,7 @@ inline const char* error_to_string(Error error) noexcept case Error::server_unsupported: return "The server does not implement the minimum features to be supported"; case Error::protocol_value_error: return "A field in a message had an unexpected value"; case Error::unknown_auth_plugin: return "The user employs an authentication plugin unknown to the client"; + case Error::wrong_num_params: return "The provided parameter count does not match the prepared statement parameter count"; #include "mysql/impl/server_error_descriptions.hpp" diff --git a/include/mysql/impl/network_algorithms/prepare_statement.ipp b/include/mysql/impl/network_algorithms/prepare_statement.ipp index 94ac323d..9494ccbc 100644 --- a/include/mysql/impl/network_algorithms/prepare_statement.ipp +++ b/include/mysql/impl/network_algorithms/prepare_statement.ipp @@ -29,21 +29,32 @@ void mysql::detail::prepare_statement( std::uint8_t msg_type = 0; std::tie(err, msg_type) = deserialize_message_type(ctx); if (err) return; - if (msg_type == 0) - { - com_stmt_prepare_ok_packet response; - err = deserialize_message(response, ctx); - if (err) return; - output = prepared_statement(channel, response); - } - else if (msg_type == error_packet_header) + + if (msg_type == error_packet_header) { err = process_error_packet(ctx, info); + return; } - else + else if (msg_type != 0) { err = make_error_code(Error::protocol_value_error); + return; } + + com_stmt_prepare_ok_packet response; + err = deserialize_message(response, ctx); + if (err) return; + + // Server sends now one packet per parameter and field. + // We ignore these for now. TODO: do sth useful with these + for (unsigned i = 0; i < response.num_columns.value + response.num_params.value; ++i) + { + channel.read(buff, err); + if (err) return; + } + + // Compose response + output = prepared_statement(channel, response); } diff --git a/include/mysql/impl/prepared_statement.ipp b/include/mysql/impl/prepared_statement.ipp index a0351880..7e280226 100644 --- a/include/mysql/impl/prepared_statement.ipp +++ b/include/mysql/impl/prepared_statement.ipp @@ -2,6 +2,7 @@ #define INCLUDE_MYSQL_IMPL_PREPARED_STATEMENT_IPP_ #include "mysql/impl/network_algorithms/execute_statement.hpp" +#include "mysql/impl/stringize.hpp" template template @@ -15,15 +16,29 @@ mysql::resultset mysql::prepared_statement::execute( assert(valid()); mysql::resultset res; - detail::execute_statement( - *channel_, - stmt_msg_.statement_id.value, - params_first, - params_last, - res, - err, - info - ); + + auto param_count = std::distance(params_first, params_last); + if (param_count != num_params()) + { + err = detail::make_error_code(Error::wrong_num_params); + info.set_message(detail::stringize( + "prepared_statement::execute: expected ", num_params(), " params, but got ", param_count)); + } + else + { + err.clear(); + info.clear(); + detail::execute_statement( + *channel_, + stmt_msg_.statement_id.value, + params_first, + params_last, + res, + err, + info + ); + } + return res; } diff --git a/include/mysql/impl/stringize.hpp b/include/mysql/impl/stringize.hpp new file mode 100644 index 00000000..093194f1 --- /dev/null +++ b/include/mysql/impl/stringize.hpp @@ -0,0 +1,25 @@ +#ifndef INCLUDE_MYSQL_IMPL_STRINGIZE_HPP_ +#define INCLUDE_MYSQL_IMPL_STRINGIZE_HPP_ + +#include +#include + +namespace mysql +{ +namespace detail +{ + +template +std::string stringize(const Types&... inputs) +{ + std::ostringstream ss; + (ss << ... << inputs); + return ss.str(); +} + +} +} + + + +#endif /* INCLUDE_MYSQL_IMPL_STRINGIZE_HPP_ */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f4b6455c..b7dbd4ee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable( integration/handshake.cpp integration/query.cpp integration/prepare_statement.cpp + integration/execute_statement.cpp integration/query_types.cpp ) target_link_libraries( diff --git a/test/integration/execute_statement.cpp b/test/integration/execute_statement.cpp new file mode 100644 index 00000000..46389acb --- /dev/null +++ b/test/integration/execute_statement.cpp @@ -0,0 +1,56 @@ +/* + * execute_statement.cpp + * + * Created on: Feb 5, 2020 + * Author: ruben + */ + +#include "integration_test_common.hpp" +#include + +using namespace mysql::test; +using mysql::value; +using mysql::Error; + +namespace +{ + +struct ExecuteStatementTest : public IntegTestAfterHandshake +{ +}; + +TEST_F(ExecuteStatementTest, IteratorsSyncErrc_OkNoParams) +{ + std::forward_list params; + auto stmt = conn.prepare_statement("SELECT * FROM empty_table"); + auto result = stmt.execute(params.begin(), params.end(), errc, info); + validate_no_error(); + EXPECT_TRUE(result.valid()); +} + +TEST_F(ExecuteStatementTest, IteratorsSyncErrc_OkWithParams) +{ + std::forward_list params { value("item"), value(42) }; + auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = stmt.execute(params.begin(), params.end(), errc, info); + validate_no_error(); + EXPECT_TRUE(result.valid()); +} + +TEST_F(ExecuteStatementTest, IteratorsSyncErrc_Error) +{ + std::forward_list params { value("item") }; + auto stmt = conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); + auto result = stmt.execute(params.begin(), params.end(), errc, info); + validate_sync_fail(Error::wrong_num_params, {"param", "2", "1", "statement", "execute"}); + EXPECT_FALSE(result.valid()); +} + +// prepared_statement::execute +// OK, no params +// OK, with params +// OK, select, insert, update, delete +// Error, wrong number of parameters +// Collection version + +}