as_netresult completion token for tests

Restructured handshake integration tests
Removed er_connection infrastructure
Reworked network_result
Greatly simplified integration tests
This commit is contained in:
Anarthal (Rubén Pérez) 2024-07-23 18:27:30 +02:00 committed by GitHub
parent 0060494170
commit a8c992dd95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 3043 additions and 3934 deletions

View File

@ -381,7 +381,7 @@ public:
BOOST_MYSQL_DECL bool ssl_active() const;
BOOST_MYSQL_DECL bool backslash_escapes() const;
BOOST_MYSQL_DECL system::result<character_set> current_character_set() const;
BOOST_MYSQL_DECL diagnostics& shared_diag(); // TODO: get rid of this
BOOST_MYSQL_DECL diagnostics& shared_diag();
engine& get_engine()
{

View File

@ -1,124 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETFUN_HELPERS_HPP
#define BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETFUN_HELPERS_HPP
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <type_traits>
#include "test_common/create_diagnostics.hpp"
#include "test_common/network_result.hpp"
#include "test_common/tracker_executor.hpp"
// Helper functions and classes to implement netmakers
// (the insfrastructure to run sync and async code as parameterized tests)
// for both unit and integ tests
namespace boost {
namespace mysql {
namespace test {
// Completion callback that saves result into a network_result
// and binds it to an executor
template <class R>
class as_network_result
{
network_result<R>* netresult_;
tracker_executor_result ex_;
void check_executor() const
{
BOOST_TEST(!is_initiation_function());
BOOST_TEST(current_executor_id() == ex_.executor_id);
}
public:
as_network_result(network_result<R>& netresult, asio::any_io_executor exec)
: netresult_(&netresult), ex_(create_tracker_executor(std::move(exec)))
{
}
using executor_type = asio::any_io_executor;
asio::any_io_executor get_executor() const { return ex_.ex; }
void operator()(error_code ec) const
{
check_executor();
netresult_->err = ec;
}
template <class Arg>
void operator()(error_code ec, Arg&& arg) const
{
check_executor();
netresult_->err = ec;
netresult_->value = std::forward<Arg>(arg);
}
};
template <class Fn, class... Args>
auto invoke_polyfill(Fn fn, Args&&... args) ->
typename std::enable_if<
std::is_function<typename std::remove_pointer<Fn>::type>::value,
decltype(fn(std::forward<Args>(args)...))>::type
{
return fn(std::forward<Args>(args)...);
}
template <class Pmem, class Obj, class... Args>
auto invoke_polyfill(Pmem fn, Obj& obj, Args&&... args) ->
typename std::enable_if<
std::is_member_function_pointer<Pmem>::value,
decltype((obj.*fn)(std::forward<Args>(args)...))>::type
{
return (obj.*fn)(std::forward<Args>(args)...);
}
template <class T, class... InvokeArgs>
void invoke_and_assign(network_result<T>& output, InvokeArgs&&... args)
{
output.value = invoke_polyfill(std::forward<InvokeArgs>(args)...);
}
template <class... InvokeArgs>
void invoke_and_assign(network_result<void>&, InvokeArgs&&... args)
{
invoke_polyfill(std::forward<InvokeArgs>(args)...);
}
template <class R>
network_result<R> create_initial_netresult(bool with_diag = true)
{
network_result<R> res(boost::mysql::make_error_code(boost::mysql::common_server_errc::er_no));
if (with_diag)
res.diag = create_server_diag("diagnostics not cleared properly");
return res;
}
inline boost::asio::io_context& get_context(boost::asio::any_io_executor ex) noexcept
{
return static_cast<boost::asio::io_context&>(ex.context());
}
inline void run_until_completion(boost::asio::any_io_executor ex)
{
auto& ctx = get_context(ex);
ctx.restart();
ctx.run();
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -8,18 +8,52 @@
#ifndef BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETFUN_MAKER_HPP
#define BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETFUN_MAKER_HPP
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/system/system_error.hpp>
#include "test_common/netfun_helpers.hpp"
#include "test_common/tracker_executor.hpp"
#include <type_traits>
#include "test_common/create_diagnostics.hpp"
#include "test_common/network_result.hpp"
namespace boost {
namespace mysql {
namespace test {
template <class Fn, class... Args>
auto invoke_polyfill(Fn fn, Args&&... args) ->
typename std::enable_if<
std::is_function<typename std::remove_pointer<Fn>::type>::value,
decltype(fn(std::forward<Args>(args)...))>::type
{
return fn(std::forward<Args>(args)...);
}
template <class Pmem, class Obj, class... Args>
auto invoke_polyfill(Pmem fn, Obj& obj, Args&&... args) ->
typename std::enable_if<
std::is_member_function_pointer<Pmem>::value,
decltype((obj.*fn)(std::forward<Args>(args)...))>::type
{
return (obj.*fn)(std::forward<Args>(args)...);
}
template <class T, class... InvokeArgs>
void invoke_and_assign(network_result<T>& output, InvokeArgs&&... args)
{
output.value = invoke_polyfill(std::forward<InvokeArgs>(args)...);
}
template <class... InvokeArgs>
void invoke_and_assign(network_result<void>&, InvokeArgs&&... args)
{
invoke_polyfill(std::forward<InvokeArgs>(args)...);
}
template <class R, class IOObject, class... Args>
struct netfun_maker_impl
{
@ -29,8 +63,21 @@ struct netfun_maker_impl
static signature sync_errc(Pfn fn)
{
return [fn](IOObject& obj, Args... args) {
auto res = create_initial_netresult<R>();
invoke_and_assign(res, fn, obj, std::forward<Args>(args)..., res.err, *res.diag);
network_result<R> res{
common_server_errc::er_no,
create_server_diag("diagnostics not cleared properly")
};
invoke_and_assign(res, fn, obj, std::forward<Args>(args)..., res.err, res.diag);
return res;
};
}
template <class Pfn>
static signature sync_errc_nodiag(Pfn fn)
{
return [fn](IOObject& obj, Args... args) {
network_result<R> res{common_server_errc::er_no, create_server_diag("<diagnostics unavailable>")};
invoke_and_assign(res, fn, obj, std::forward<Args>(args)..., res.err);
return res;
};
}
@ -58,118 +105,44 @@ struct netfun_maker_impl
}
template <class Pfn>
static signature sync_errc_noerrinfo(Pfn fn)
static signature async_diag(Pfn fn)
{
return [fn](IOObject& obj, Args... args) {
auto res = create_initial_netresult<R>(false);
invoke_and_assign(res, fn, obj, std::forward<Args>(args)..., res.err);
return res;
diagnostics diag; // checks for clearing diag are performed by as_netresult
return invoke_polyfill(fn, obj, std::forward<Args>(args)..., diag, as_netresult).run();
};
}
template <class Pfn>
static signature async_errinfo(Pfn fn)
static signature async_nodiag(Pfn fn)
{
return [fn](IOObject& obj, Args... args) {
// Get the object's I/O executor
auto ex = obj.get_executor();
// Create the initial result
auto res = create_initial_netresult<R>();
{
// Mark ourselves as initiating
initiation_guard guard;
// Invoke the initiation function
invoke_polyfill(
fn,
obj,
std::forward<Args>(args)...,
*res.diag,
as_network_result<R>(res, ex)
);
}
// Run
run_until_completion(ex);
// Done
return res;
};
}
template <class Pfn>
static signature async_noerrinfo(Pfn fn)
{
return [fn](IOObject& obj, Args... args) {
// Get the object's I/O executor
auto ex = obj.get_executor();
// Create the initial result
auto res = create_initial_netresult<R>(false);
{
// Mark ourselves as initiating
initiation_guard guard;
// Invoke the initiation function
invoke_polyfill(fn, obj, std::forward<Args>(args)..., as_network_result<R>(res, ex));
}
// Run until completion
run_until_completion(ex);
// Done
return res;
return invoke_polyfill(fn, obj, std::forward<Args>(args)..., as_netresult).run();
};
}
};
template <class R, class Obj, class... Args>
class netfun_maker_mem
class netfun_maker
{
using impl = netfun_maker_impl<R, Obj&, Args...>;
public:
using signature = std::function<network_result<R>(Obj&, Args...)>;
using sig_sync_errc = R (Obj::*)(Args..., error_code&, diagnostics&);
using sig_sync_errc_noerrinfo = R (Obj::*)(Args..., error_code&);
using sig_sync_errc_nodiag = R (Obj::*)(Args..., error_code&);
using sig_sync_errc_nodiag_old =
error_code (Obj::*)(Args..., error_code&); // support old Asio signatures
using sig_sync_exc = R (Obj::*)(Args...);
using sig_async_errinfo = void (Obj::*)(Args..., diagnostics&, as_network_result<R>&&);
using sig_async_noerrinfo = void (Obj::*)(Args..., as_network_result<R>&&);
using sig_async_diag = runnable_network_result<R> (Obj::*)(Args..., diagnostics&, const as_netresult_t&);
using sig_async_nodiag = runnable_network_result<R> (Obj::*)(Args..., const as_netresult_t&);
static signature sync_errc(sig_sync_errc pfn) { return impl::sync_errc(pfn); }
static signature sync_errc_noerrinfo(sig_sync_errc_noerrinfo pfn)
{
return impl::sync_errc_noerrinfo(pfn);
}
static signature sync_errc_nodiag(sig_sync_errc_nodiag pfn) { return impl::sync_errc_nodiag(pfn); }
static signature sync_errc_nodiag(sig_sync_errc_nodiag_old pfn) { return impl::sync_errc_nodiag(pfn); }
static signature sync_exc(sig_sync_exc pfn) { return impl::sync_exc(pfn); }
static signature async_errinfo(sig_async_errinfo pfn) { return impl::async_errinfo(pfn); }
static signature async_noerrinfo(sig_async_noerrinfo pfn) { return impl::async_noerrinfo(pfn); }
};
template <class R, class... Args>
class netfun_maker_fn
{
using impl = netfun_maker_impl<R, Args...>;
public:
using signature = std::function<network_result<R>(Args...)>;
using sig_sync_errc = R (*)(Args..., error_code&, diagnostics&);
using sig_sync_errc_noerrinfo = R (*)(Args..., error_code&);
using sig_sync_exc = R (*)(Args...);
using sig_async_errinfo = void (*)(Args..., diagnostics&, as_network_result<R>&&);
using sig_async_noerrinfo = void (*)(Args..., as_network_result<R>&&);
static signature sync_errc(sig_sync_errc pfn) { return impl::sync_errc(pfn); }
static signature sync_errc_noerrinfo(sig_sync_errc_noerrinfo pfn)
{
return impl::sync_errc_noerrinfo(pfn);
}
static signature sync_exc(sig_sync_exc pfn) { return impl::sync_exc(pfn); }
static signature async_errinfo(sig_async_errinfo pfn) { return impl::async_errinfo(pfn); }
static signature async_noerrinfo(sig_async_noerrinfo pfn) { return impl::async_noerrinfo(pfn); }
static signature async_diag(sig_async_diag pfn) { return impl::async_diag(pfn); }
static signature async_nodiag(sig_async_nodiag pfn) { return impl::async_nodiag(pfn); }
};
} // namespace test

View File

@ -8,15 +8,28 @@
#ifndef BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETWORK_RESULT_HPP
#define BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_NETWORK_RESULT_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/optional/optional.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/assert/source_location.hpp>
#include <boost/test/unit_test.hpp>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "test_common/create_diagnostics.hpp"
#include "test_common/printing.hpp"
#include "test_common/source_location.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_common/validate_string_contains.hpp"
namespace boost {
@ -27,111 +40,327 @@ struct no_result
{
};
struct network_result_base
// network_result: system::result-like type with helper functions
// for tests
struct BOOST_ATTRIBUTE_NODISCARD network_result_base
{
error_code err;
boost::optional<diagnostics> diag; // some network_function's don't provide this
diagnostics diag;
network_result_base() = default;
network_result_base(error_code ec) : err(ec) {}
network_result_base(error_code ec, diagnostics&& diag) : err(ec), diag(std::move(diag)) {}
void validate_no_error() const
void validate_no_error(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) const
{
BOOST_TEST_CONTEXT("diagnostics= " << server_diag() << ", error_code=" << err.message())
validate_error(error_code(), diagnostics(), loc);
}
// Use for functions without a diagnostics& parameter
void validate_no_error_nodiag(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) const
{
validate_error(error_code(), create_server_diag("<diagnostics unavailable>"), loc);
}
void validate_error(
error_code expected_err,
const diagnostics& expected_diag = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
) const
{
BOOST_TEST_CONTEXT("Called from " << loc)
{
BOOST_TEST_REQUIRE(err == error_code());
if (diag)
{
BOOST_TEST(diag->server_message() == "");
}
BOOST_TEST(diag == expected_diag);
BOOST_TEST_REQUIRE(err == expected_err);
}
}
void validate_error(
common_server_errc expected_err,
string_view expected_msg = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
)
{
validate_error(expected_err, create_server_diag(expected_msg), loc);
}
void validate_error(
client_errc expected_err,
string_view expected_msg = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
)
{
validate_error(expected_err, create_client_diag(expected_msg), loc);
}
// Use when the exact message isn't known, but some of its contents are
void validate_error_contains(
error_code expected_err,
const std::vector<std::string>& pieces,
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
)
{
BOOST_TEST_CONTEXT("Called from " << loc)
{
validate_string_contains(diag.server_message(), pieces);
BOOST_TEST_REQUIRE(err == expected_err);
}
}
// Use when you don't care or can't determine the kind of error
void validate_any_error(const std::vector<std::string>& expected_msg = {}) const
void validate_any_error(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) const
{
BOOST_TEST_CONTEXT("diagnostics= " << server_diag() << ", error_code=" << err.message())
{
BOOST_TEST_REQUIRE(err != error_code());
if (diag)
{
validate_string_contains(diag->server_message(), expected_msg);
}
}
}
void validate_error(error_code expected_errc, const std::vector<std::string>& expected_msg) const
{
BOOST_TEST_CONTEXT("diagnostics= " << server_diag() << ", error_code=" << err.message())
{
BOOST_TEST_REQUIRE(err == expected_errc);
if (diag)
{
validate_string_contains(diag->server_message(), expected_msg);
}
}
}
void validate_error_exact(error_code expected_err, const char* expected_msg = "")
{
BOOST_TEST_CONTEXT("diagnostics= " << server_diag() << ", error_code=" << err.message())
{
BOOST_TEST_REQUIRE(err == expected_err);
if (diag)
{
BOOST_TEST(diag->server_message() == expected_msg);
}
}
}
void validate_error_exact_client(error_code expected_err, const char* expected_msg = "")
{
BOOST_TEST_CONTEXT("diagnostics= " << client_diag() << ", error_code=" << err.message())
{
BOOST_TEST_REQUIRE(err == expected_err);
if (diag)
{
BOOST_TEST(diag->client_message() == expected_msg);
}
}
}
private:
string_view server_diag() const noexcept
{
return diag ? diag->server_message() : string_view("<unavailable>");
}
string_view client_diag() const noexcept
{
return diag ? diag->client_message() : string_view("<unavailable>");
BOOST_TEST_CONTEXT("Called from " << loc) { BOOST_TEST_REQUIRE(err != error_code()); }
}
};
template <class T>
struct network_result : network_result_base
template <class R>
struct BOOST_ATTRIBUTE_NODISCARD network_result : network_result_base
{
using value_type = typename std::conditional<std::is_same<T, void>::value, no_result, T>::type;
using value_type = typename std::conditional<std::is_same<R, void>::value, no_result, R>::type;
value_type value;
network_result() = default;
network_result(error_code ec, diagnostics&& info, value_type&& value = {})
: network_result_base(ec, std::move(info)), value(std::move(value))
{
}
network_result(error_code ec, value_type&& value = {}) : network_result_base(ec), value(std::move(value))
network_result(error_code ec, diagnostics diag, value_type value = {})
: network_result_base{ec, std::move(diag)}, value(std::move(value))
{
}
const value_type& get() const
BOOST_ATTRIBUTE_NODISCARD
value_type get(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) const
{
validate_no_error();
validate_no_error(loc);
return value;
}
};
// Wraps a network_result and an executor. The result of as_netresult_t.
template <class R>
struct BOOST_ATTRIBUTE_NODISCARD runnable_network_result
{
struct impl_t
{
network_result<R> netres{
common_server_errc::er_no,
create_server_diag("network_result_v2 - diagnostics not cleared")
};
};
std::unique_ptr<impl_t> impl;
runnable_network_result() : impl(new impl_t) {}
network_result<R> run() &&
{
run_global_context();
return std::move(impl->netres);
}
void validate_no_error(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) &&
{
std::move(*this).run().validate_no_error(loc);
}
void validate_no_error_nodiag(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) &&
{
std::move(*this).run().validate_no_error_nodiag(loc);
}
void validate_error(
error_code expected_err,
const diagnostics& expected_diag = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
) &&
{
std::move(*this).run().validate_error(expected_err, expected_diag, loc);
}
void validate_error(
common_server_errc expected_err,
string_view expected_msg = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
) &&
{
std::move(*this).run().validate_error(expected_err, expected_msg, loc);
}
void validate_error(
client_errc expected_err,
string_view expected_msg = {},
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
) &&
{
std::move(*this).run().validate_error(expected_err, expected_msg, loc);
}
// Use when the exact message isn't known, but some of its contents are
void validate_error_contains(
error_code expected_err,
const std::vector<std::string>& pieces,
source_location loc = BOOST_MYSQL_CURRENT_LOCATION
) &&
{
std::move(*this).run().validate_error_contains(expected_err, pieces, loc);
}
// Use when you don't care or can't determine the kind of error
void validate_any_error(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) &&
{
std::move(*this).run().validate_any_error(loc);
}
BOOST_ATTRIBUTE_NODISCARD
typename network_result<R>::value_type get(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) &&
{
return std::move(*this).run().get(loc);
}
};
struct as_netresult_t
{
asio::cancellation_slot slot;
};
constexpr as_netresult_t as_netresult{};
namespace test_detail {
template <class Signature>
struct as_netres_sig_to_rtype;
template <>
struct as_netres_sig_to_rtype<void(error_code)>
{
using type = void;
};
template <class T>
struct as_netres_sig_to_rtype<void(error_code, T)>
{
using type = T;
};
template <class R>
class as_netres_handler
{
network_result<R>* target_;
tracker_executor_result ex_;
asio::cancellation_slot slot_;
const diagnostics* diag_ptr;
void complete(error_code ec) const
{
// Check executor
BOOST_TEST(!is_initiation_function());
BOOST_TEST(current_executor_id() == ex_.executor_id);
// Assign error code and diagnostics
target_->err = ec;
if (diag_ptr)
target_->diag = *diag_ptr;
else
target_->diag = create_server_diag("<diagnostics unavailable>");
}
public:
as_netres_handler(
network_result<R>& netresult,
const diagnostics* output_diag,
asio::cancellation_slot slot
)
: target_(&netresult),
ex_(create_tracker_executor(global_context_executor())),
slot_(slot),
diag_ptr(output_diag)
{
}
// Executor
using executor_type = asio::any_io_executor;
asio::any_io_executor get_executor() const { return ex_.ex; }
// Cancellation slot
using cancellation_slot_type = asio::cancellation_slot;
asio::cancellation_slot get_cancellation_slot() const noexcept { return slot_; }
void operator()(error_code ec) const { complete(ec); }
template <class Arg>
void operator()(error_code ec, Arg&& arg) const
{
target_->value = std::forward<Arg>(arg);
complete(ec);
}
};
} // namespace test_detail
} // namespace test
} // namespace mysql
} // namespace boost
namespace boost {
namespace asio {
template <typename Signature>
class async_result<mysql::test::as_netresult_t, Signature>
{
public:
using R = typename mysql::test::test_detail::as_netres_sig_to_rtype<Signature>::type;
using return_type = mysql::test::runnable_network_result<R>;
template <typename Initiation, typename... Args>
static return_type initiate(Initiation&& initiation, mysql::test::as_netresult_t token, Args&&... args)
{
return do_initiate(std::move(initiation), token.slot, std::move(args)...);
}
private:
// initiate() is not allowed to inspect individual arguments
template <typename Initiation, typename... Args>
static return_type do_initiate(
Initiation&& initiation,
asio::cancellation_slot slot,
mysql::diagnostics* diag,
Args&&... args
)
{
// Verify that we correctly set diagnostics in all cases
*diag = mysql::test::create_server_diag("Diagnostics not cleared properly");
// Create the return type
mysql::test::runnable_network_result<R> netres;
// Record that we're initiating
mysql::test::initiation_guard guard;
// Actually call the initiation function
std::move(initiation)(
mysql::test::test_detail::as_netres_handler<R>(netres.impl->netres, diag, slot),
diag,
std::move(args)...
);
return netres;
}
// For functions without diagnostics
template <typename Initiation, typename... Args>
static return_type do_initiate(Initiation&& initiation, asio::cancellation_slot slot, Args&&... args)
{
// Create the return type
mysql::test::runnable_network_result<R> netres;
// Record that we're initiating
mysql::test::initiation_guard guard;
// Actually call the initiation function
std::move(initiation)(
mysql::test::test_detail::as_netres_handler<R>(netres.impl->netres, nullptr, slot),
std::move(args)...
);
return netres;
}
};
} // namespace asio
} // namespace boost
#endif

View File

@ -0,0 +1,22 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_SOURCE_LOCATION_HPP
#define BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_SOURCE_LOCATION_HPP
#include <boost/assert/source_location.hpp>
// boost::source_location triggers a bug on gcc < 8 when using PCHs
// BOOST_CURRENT_LOCATION complains about a redefinition of __PRETTY_FUNCTION__
// when used as default argument
#if defined(BOOST_GCC) && BOOST_GCC < 80000
#define BOOST_MYSQL_CURRENT_LOCATION ::boost::source_location(__FILE__, __LINE__, "")
#else
#define BOOST_MYSQL_CURRENT_LOCATION BOOST_CURRENT_LOCATION
#endif
#endif

View File

@ -47,6 +47,9 @@ struct initiation_guard
};
bool is_initiation_function();
asio::any_io_executor global_context_executor();
void run_global_context();
} // namespace test
} // namespace mysql
} // namespace boost

View File

@ -5,9 +5,11 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/execution/blocking.hpp>
#include <boost/asio/execution/relationship.hpp>
#include <boost/asio/execution_context.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/require.hpp>
#include <boost/test/unit_test.hpp>
@ -226,7 +228,7 @@ int boost::mysql::test::current_executor_id()
boost::mysql::test::initiation_guard::initiation_guard()
{
BOOST_TEST_REQUIRE(!g_is_running_initiation);
BOOST_ASSERT(!g_is_running_initiation);
g_is_running_initiation = true;
}
@ -236,4 +238,14 @@ boost::mysql::test::initiation_guard::~initiation_guard()
g_is_running_initiation = false;
}
bool boost::mysql::test::is_initiation_function() { return g_is_running_initiation; }
bool boost::mysql::test::is_initiation_function() { return g_is_running_initiation; }
static boost::asio::io_context g_ctx;
boost::asio::any_io_executor boost::mysql::test::global_context_executor() { return g_ctx.get_executor(); }
void boost::mysql::test::run_global_context()
{
g_ctx.restart();
g_ctx.run();
}

View File

@ -10,17 +10,13 @@ add_executable(
# Utilities
src/server_features.cpp
src/get_endpoint.cpp
src/metadata_validator.cpp
src/er_network_variant.cpp
src/sync_errc.cpp
src/sync_exc.cpp
src/async_callback.cpp
src/async_coroutines.cpp
src/async_coroutinescpp20.cpp
src/spotchecks_helpers.cpp
src/utils.cpp
# Actual tests
test/spotchecks.cpp
test/execution_requests.cpp
test/crud.cpp
test/handshake.cpp
test/prepared_statements.cpp

View File

@ -22,17 +22,13 @@ run
# Utilities
src/server_features.cpp
src/get_endpoint.cpp
src/metadata_validator.cpp
src/er_network_variant.cpp
src/sync_errc.cpp
src/sync_exc.cpp
src/async_callback.cpp
src/async_coroutines.cpp
src/async_coroutinescpp20.cpp
src/spotchecks_helpers.cpp
src/utils.cpp
# Actual tests
test/spotchecks.cpp
test/execution_requests.cpp
test/crud.cpp
test/handshake.cpp
test/prepared_statements.cpp

View File

@ -246,8 +246,9 @@ INSERT INTO types_date VALUES
("yregular_invalid_date_leap100", "1900-02-29")
;
-- A bug in MySQL 5.x requires us to set this collation to binary to get the correct order
CREATE TABLE types_datetime(
id VARCHAR(50) NOT NULL PRIMARY KEY,
id VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL PRIMARY KEY,
field_0 DATETIME(0),
field_1 DATETIME(1),
field_2 DATETIME(2),

View File

@ -5,6 +5,8 @@
-- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
--
USE boost_mysql_integtests;
-- Setup that requires the presence of SHA256 functionality
-- This one is used for unknown authentication plugin tests
DROP USER IF EXISTS 'sha2p_user'@'%';
@ -18,5 +20,17 @@ CREATE USER 'csha2p_user'@'%' IDENTIFIED WITH 'caching_sha2_password';
ALTER USER 'csha2p_user'@'%' IDENTIFIED BY 'csha2p_password';
GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'csha2p_user'@'%';
-- User that uses caching_sha2_password plugin with an empty password
DROP USER IF EXISTS 'csha2p_empty_password_user'@'%';
CREATE USER 'csha2p_empty_password_user'@'%' IDENTIFIED WITH 'caching_sha2_password';
ALTER USER 'csha2p_empty_password_user'@'%' IDENTIFIED BY '';
GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'csha2p_empty_password_user'@'%';
-- caching_sha2_password behaves differently on sha256 cache hit and miss.
-- These tests require exclusive access to the cache, and lock this table
-- to avoid race conditions (e.g. between concurrent b2 runs)
DROP TABLE IF EXISTS sha256_mutex;
CREATE TABLE sha256_mutex (dummy INT);
FLUSH PRIVILEGES;

View File

@ -0,0 +1,40 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ANY_CONNECTION_FIXTURE_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ANY_CONNECTION_FIXTURE_HPP
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/assert/source_location.hpp>
#include "test_common/source_location.hpp"
namespace boost {
namespace mysql {
namespace test {
struct any_connection_fixture
{
any_connection conn;
any_connection_fixture() : any_connection_fixture(any_connection_params{}) {}
any_connection_fixture(any_connection_params params);
any_connection_fixture(asio::ssl::context& ssl_ctx);
~any_connection_fixture();
void connect(const connect_params& params, source_location loc = BOOST_MYSQL_CURRENT_LOCATION);
void connect(source_location loc = BOOST_MYSQL_CURRENT_LOCATION);
void start_transaction(source_location loc = BOOST_MYSQL_CURRENT_LOCATION);
};
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,151 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_COMMON_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_COMMON_HPP
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/system_executor.hpp>
#include <boost/test/unit_test.hpp>
#include <initializer_list>
#include "test_common/ci_server.hpp"
#include "test_integration/er_connection.hpp"
#include "test_integration/er_network_variant.hpp"
#include "test_integration/metadata_validator.hpp"
namespace boost {
namespace mysql {
namespace test {
inline connect_params default_connect_params(ssl_mode ssl = ssl_mode::enable)
{
connect_params res;
res.server_address.emplace_host_and_port(get_hostname());
res.username = integ_user;
res.password = integ_passwd;
res.database = integ_db;
res.ssl = ssl;
return res;
}
struct network_fixture_base
{
handshake_params params{integ_user, integ_passwd, integ_db};
boost::asio::io_context ctx;
boost::asio::ssl::context ssl_ctx{boost::asio::ssl::context::tls_client};
};
struct network_fixture : network_fixture_base
{
er_network_variant* var{};
er_connection_ptr conn;
~network_fixture()
{
if (conn)
{
conn->sync_close();
}
}
void setup(er_network_variant& variant)
{
var = &variant;
conn = var->create_connection(ctx.get_executor(), ssl_ctx);
conn->set_metadata_mode(metadata_mode::full);
}
void setup_and_physical_connect(er_network_variant& net)
{
setup(net);
conn->physical_connect();
}
void setup_and_connect(er_network_variant& net, ssl_mode m = ssl_mode::require)
{
setup(net);
connect(m);
}
void set_credentials(string_view user, string_view password)
{
params.set_username(user);
params.set_password(password);
}
// Verifies that we are or are not using SSL, depending on whether the stream supports it or not
void validate_ssl(ssl_mode m = ssl_mode::require)
{
bool expected = (m == ssl_mode::require || m == ssl_mode::enable) && var->supports_ssl();
BOOST_TEST(conn->uses_ssl() == expected);
}
void handshake(ssl_mode m = ssl_mode::require)
{
assert(conn);
params.set_ssl(m);
conn->handshake(params).validate_no_error();
validate_ssl(m);
}
void connect(ssl_mode m = ssl_mode::require)
{
assert(conn);
params.set_ssl(m);
conn->connect(params).validate_no_error();
validate_ssl(m);
}
// Call this in the fixture setup of any test invoking write
// operations on the database, to prevent race conditions,
// make the testing environment more stable and speed up the tests
void start_transaction()
{
results result;
conn->execute("START TRANSACTION", result).get();
}
};
inline void validate_2fields_meta(const metadata_collection_view& fields, const std::string& table)
{
validate_meta(
fields,
{meta_validator(table, "id", column_type::int_),
meta_validator(table, "field_varchar", column_type::varchar)}
);
}
inline void validate_eof(
const execution_state& st,
unsigned affected_rows = 0,
unsigned warnings = 0,
unsigned last_insert = 0,
string_view info = ""
)
{
BOOST_TEST_REQUIRE(st.complete());
BOOST_TEST(st.affected_rows() == affected_rows);
BOOST_TEST(st.warning_count() == warnings);
BOOST_TEST(st.last_insert_id() == last_insert);
BOOST_TEST(st.info() == info);
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif /* TEST_INTEGRATION_INTEGRATION_TEST_COMMON_HPP_ */

View File

@ -0,0 +1,82 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_CONNECT_PARAMS_BUILDER_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_CONNECT_PARAMS_BUILDER_HPP
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/string_view.hpp>
#include <cstdint>
#include "test_common/ci_server.hpp"
namespace boost {
namespace mysql {
namespace test {
class connect_params_builder
{
handshake_params res_{integ_user, integ_passwd, integ_db};
any_address addr_;
public:
connect_params_builder() { addr_.emplace_host_and_port(get_hostname()); }
connect_params_builder& set_unix()
{
addr_.emplace_unix_path(default_unix_path);
return *this;
}
connect_params_builder& credentials(string_view username, string_view passwd)
{
res_.set_username(username);
res_.set_password(passwd);
return *this;
}
connect_params_builder& database(string_view db)
{
res_.set_database(db);
return *this;
}
connect_params_builder& disable_ssl() { return ssl(ssl_mode::disable); }
connect_params_builder& ssl(ssl_mode ssl)
{
res_.set_ssl(ssl);
return *this;
}
connect_params_builder& multi_queries(bool v)
{
res_.set_multi_queries(v);
return *this;
}
connect_params_builder& collation(std::uint16_t v)
{
res_.set_connection_collation(v);
return *this;
}
handshake_params build_hparams() const { return res_; }
connect_params build();
};
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,81 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ER_CONNECTION_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ER_CONNECTION_HPP
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/static_execution_state.hpp>
#include <boost/mysql/static_results.hpp>
#include <forward_list>
#include <memory>
#include "test_common/network_result.hpp"
#include "test_integration/static_rows.hpp"
namespace boost {
namespace mysql {
namespace test {
using fv_list_it = std::forward_list<field_view>::const_iterator;
class er_network_variant;
class er_connection
{
public:
virtual ~er_connection() {}
virtual bool uses_ssl() const = 0;
virtual bool is_open() const = 0;
virtual void set_metadata_mode(metadata_mode v) = 0;
virtual void physical_connect() = 0; // used by fixture setup functions
virtual void sync_close() noexcept = 0; // used by fixture cleanup functions
virtual er_network_variant& variant() const = 0;
virtual network_result<void> connect(const handshake_params&) = 0;
virtual network_result<void> handshake(const handshake_params&) = 0;
virtual network_result<statement> prepare_statement(string_view statement) = 0;
virtual network_result<void> execute(string_view, results&) = 0;
virtual network_result<void> execute(bound_statement_tuple<std::tuple<field_view, field_view>>, results&) = 0;
virtual network_result<void> execute(bound_statement_iterator_range<fv_list_it>, results&) = 0;
virtual network_result<void> start_execution(string_view, execution_state&) = 0;
virtual network_result<void> start_execution(bound_statement_tuple<std::tuple<field_view, field_view>>, execution_state&) = 0;
virtual network_result<void> start_execution(bound_statement_iterator_range<fv_list_it>, execution_state&) = 0;
virtual network_result<void> close_statement(statement&) = 0;
virtual network_result<void> read_resultset_head(execution_state& st) = 0;
virtual network_result<rows_view> read_some_rows(execution_state& st) = 0;
virtual network_result<void> ping() = 0;
virtual network_result<void> reset_connection() = 0;
virtual network_result<void> quit() = 0;
virtual network_result<void> close() = 0;
#ifdef BOOST_MYSQL_CXX14
using static_results_t = static_results<row_multifield, row_2fields, empty>;
using static_state_t = static_execution_state<row_multifield, row_2fields, empty>;
virtual network_result<void> execute(string_view, static_results_t&) = 0;
virtual network_result<void> start_execution(string_view, static_state_t&) = 0;
virtual network_result<void> read_resultset_head(static_state_t& st) = 0;
virtual network_result<std::size_t> read_some_rows(static_state_t& st, boost::span<row_multifield>) = 0;
virtual network_result<std::size_t> read_some_rows(static_state_t& st, boost::span<row_2fields>) = 0;
#endif
};
using er_connection_ptr = std::unique_ptr<er_connection>;
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,51 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ER_NETWORK_VARIANT_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_ER_NETWORK_VARIANT_HPP
#include <boost/mysql/string_view.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/core/span.hpp>
#include <functional>
#include <iosfwd>
#include "test_integration/er_connection.hpp"
namespace boost {
namespace mysql {
namespace test {
class er_network_variant
{
public:
virtual ~er_network_variant() {}
virtual bool supports_ssl() const = 0;
virtual bool is_unix_socket() const = 0;
virtual bool supports_handshake() const = 0;
virtual const char* stream_name() const = 0;
virtual const char* variant_name() const = 0;
std::string name() const { return std::string(stream_name()) + '_' + variant_name(); }
virtual er_connection_ptr create_connection(boost::asio::any_io_executor, boost::asio::ssl::context&) = 0;
};
std::ostream& operator<<(std::ostream& os, const er_network_variant& var);
std::vector<std::reference_wrapper<er_network_variant>> all_variants();
std::vector<std::reference_wrapper<er_network_variant>> all_variants_with_handshake();
er_network_variant& get_network_variant(string_view name);
std::vector<std::reference_wrapper<er_network_variant>> get_network_variants(
boost::span<const string_view> names
);
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,47 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_GET_ENDPOINT_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_GET_ENDPOINT_HPP
#include <boost/mysql/string_view.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
namespace boost {
namespace mysql {
namespace test {
template <class Protocol>
struct endpoint_getter;
template <>
struct endpoint_getter<boost::asio::ip::tcp>
{
boost::asio::ip::tcp::endpoint operator()();
};
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
template <>
struct endpoint_getter<boost::asio::local::stream_protocol>
{
boost::asio::local::stream_protocol::endpoint operator()();
};
#endif
template <class Stream>
typename Stream::lowest_layer_type::endpoint_type get_endpoint()
{
return endpoint_getter<typename Stream::lowest_layer_type::protocol_type>()();
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -73,10 +73,8 @@ private:
std::vector<flag_getter> ignore_flags_;
};
void validate_meta(
const metadata_collection_view& actual,
const std::vector<meta_validator>& expected
);
void validate_meta(const metadata_collection_view& actual, const std::vector<meta_validator>& expected);
void validate_2fields_meta(metadata_collection_view fields, string_view table);
} // namespace test
} // namespace mysql

View File

@ -1,84 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_NETWORK_SAMPLES_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_NETWORK_SAMPLES_HPP
#include <boost/mysql/string_view.hpp>
#include <boost/test/data/monomorphic/fwd.hpp>
#include <boost/test/data/size.hpp>
#include <functional>
#include <type_traits>
#include <vector>
#include "test_integration/er_network_variant.hpp"
namespace boost {
namespace mysql {
namespace test {
// A Boost.Test dataset of er_network_variant samples that are lazily computed.
// Laziness is required because creating the variants requires access to
// the enabled server features (and thus getenv), and that's not legal from global initializers.
class network_samples
{
using col_t = std::vector<std::reference_wrapper<er_network_variant>>;
using factory_t = std::function<col_t()>;
mutable bool created_{false};
mutable col_t data_;
factory_t fn_;
col_t& get() const
{
if (!created_)
{
data_ = fn_();
created_ = true;
}
return data_;
}
public:
static constexpr int arity = 1;
using iterator = col_t::const_iterator;
network_samples(factory_t fn) : fn_(std::move(fn)) {}
network_samples(std::vector<string_view> names)
: network_samples([names]() { return get_network_variants(names); })
{
}
static network_samples all() { return network_samples(&all_variants); }
static network_samples all_with_handshake() { return network_samples(&all_variants_with_handshake); }
boost::unit_test::data::size_t size() const { return get().size(); }
iterator begin() const { return get().begin(); }
};
} // namespace test
} // namespace mysql
} // namespace boost
namespace boost {
namespace unit_test {
namespace data {
namespace monomorphic {
template <>
struct is_dataset<mysql::test::network_samples> : std::true_type
{
};
} // namespace monomorphic
} // namespace data
} // namespace unit_test
} // namespace boost
#endif

View File

@ -0,0 +1,203 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_SPOTCHECKS_HELPERS_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_SPOTCHECKS_HELPERS_HPP
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/static_execution_state.hpp>
#include <boost/mysql/static_results.hpp>
#include <boost/mysql/tcp.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/core/span.hpp>
#include <iosfwd>
#include "test_common/netfun_maker.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/static_rows.hpp"
#include "test_integration/tcp_connection_fixture.hpp"
namespace boost {
namespace mysql {
namespace test {
#ifdef BOOST_MYSQL_CXX14
using static_results_t = static_results<row_multifield, row_2fields, empty>;
using static_state_t = static_execution_state<row_multifield, row_2fields, empty>;
#endif
// Netmakers
template <class Conn>
struct netmakers_common
{
using prepare_statement = netfun_maker<statement, Conn, string_view>;
using execute_query = netfun_maker<void, Conn, const string_view&, results&>;
using execute_statement = netfun_maker<
void,
Conn,
const bound_statement_tuple<std::tuple<int, int>>&,
results&>;
using start_execution = netfun_maker<void, Conn, const string_view&, execution_state&>;
using close_statement = netfun_maker<void, Conn, const statement&>;
using read_resultset_head = netfun_maker<void, Conn, execution_state&>;
using read_some_rows = netfun_maker<rows_view, Conn, execution_state&>;
using ping = netfun_maker<void, Conn>;
using reset_connection = netfun_maker<void, Conn>;
using close = netfun_maker<void, Conn>;
#ifdef BOOST_MYSQL_CXX14
using execute_static = netfun_maker<void, Conn, const string_view&, static_results_t&>;
using start_execution_static = netfun_maker<void, Conn, const string_view&, static_state_t&>;
using read_resultset_head_static = netfun_maker<void, Conn, static_state_t&>;
using read_some_rows_static_1 = netfun_maker<
std::size_t,
Conn,
static_state_t&,
boost::span<row_multifield>>;
using read_some_rows_static_2 = netfun_maker<
std::size_t,
Conn,
static_state_t&,
boost::span<row_2fields>>;
#endif
};
struct netmakers_connection : netmakers_common<tcp_connection>
{
using connect_stream = netfun_maker<void, asio::ip::tcp::socket, const asio::ip::tcp::endpoint&>;
using handshake = netfun_maker<void, tcp_connection, const handshake_params&>;
using connect = netfun_maker<
void,
tcp_connection,
const asio::ip::tcp::endpoint&,
const handshake_params&>;
using quit = netfun_maker<void, tcp_connection>;
};
struct netmakers_any : netmakers_common<any_connection>
{
using connect = netfun_maker<void, any_connection, const connect_params&>;
using set_character_set = netfun_maker<void, any_connection, const character_set&>;
using run_pipeline = netfun_maker<
void,
any_connection,
const pipeline_request&,
std::vector<stage_response>&>;
};
struct network_functions_connection
{
using netmakers = netmakers_connection;
string_view name;
netmakers::prepare_statement::signature prepare_statement;
netmakers::execute_query::signature execute_query;
netmakers::execute_statement::signature execute_statement;
netmakers::start_execution::signature start_execution;
netmakers::close_statement::signature close_statement;
netmakers::read_resultset_head::signature read_resultset_head;
netmakers::read_some_rows::signature read_some_rows;
netmakers::ping::signature ping;
netmakers::reset_connection::signature reset_connection;
netmakers::close::signature close;
#ifdef BOOST_MYSQL_CXX14
netmakers::execute_static::signature execute_static;
netmakers::start_execution_static::signature start_execution_static;
netmakers::read_resultset_head_static::signature read_resultset_head_static;
netmakers::read_some_rows_static_1::signature read_some_rows_static_1;
netmakers::read_some_rows_static_2::signature read_some_rows_static_2;
#endif
netmakers::connect_stream::signature connect_stream;
netmakers::handshake::signature handshake;
netmakers::connect::signature connect;
netmakers::quit::signature quit;
static std::vector<network_functions_connection> all();
static std::vector<network_functions_connection> sync_and_async();
};
std::ostream& operator<<(std::ostream& os, const network_functions_connection& v);
struct network_functions_any
{
using netmakers = netmakers_any;
string_view name;
netmakers::prepare_statement::signature prepare_statement;
netmakers::execute_query::signature execute_query;
netmakers::execute_statement::signature execute_statement;
netmakers::start_execution::signature start_execution;
netmakers::close_statement::signature close_statement;
netmakers::read_resultset_head::signature read_resultset_head;
netmakers::read_some_rows::signature read_some_rows;
netmakers::ping::signature ping;
netmakers::reset_connection::signature reset_connection;
netmakers::close::signature close;
#ifdef BOOST_MYSQL_CXX14
netmakers::execute_static::signature execute_static;
netmakers::start_execution_static::signature start_execution_static;
netmakers::read_resultset_head_static::signature read_resultset_head_static;
netmakers::read_some_rows_static_1::signature read_some_rows_static_1;
netmakers::read_some_rows_static_2::signature read_some_rows_static_2;
#endif
netmakers::connect::signature connect;
netmakers::set_character_set::signature set_character_set;
netmakers::run_pipeline::signature run_pipeline;
static std::vector<network_functions_any> all();
static std::vector<network_functions_any> sync_and_async();
};
std::ostream& operator<<(std::ostream& os, const network_functions_any& v);
// Fixtures. Like tcp_connection_fixture and any_connection_fixture,
// but using a network_functions value
struct netfn_fixture_connection
{
tcp_connection conn{global_context_executor()};
network_functions_connection net;
netfn_fixture_connection(network_functions_connection n) : net(std::move(n))
{
conn.set_meta_mode(metadata_mode::full);
}
~netfn_fixture_connection() { net.close(conn).validate_no_error(); }
void connect(connect_params_builder conn_params = {})
{
net.connect(conn, get_tcp_endpoint(), conn_params.build_hparams()).validate_no_error();
}
};
struct netfn_fixture_any
{
any_connection conn{global_context_executor()};
network_functions_any net;
netfn_fixture_any(network_functions_any n) : net(std::move(n))
{
conn.set_meta_mode(metadata_mode::full);
}
~netfn_fixture_any() { net.close(conn).validate_no_error(); }
void connect(connect_params_builder conn_params = {})
{
net.connect(conn, conn_params.build()).validate_no_error();
}
};
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,107 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_STREAMS_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_STREAMS_HPP
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/use_future.hpp>
namespace boost {
namespace mysql {
namespace test {
// The actual streams we will be using to test
using tcp_socket = boost::asio::ip::tcp::socket;
using tcp_ssl_socket = boost::asio::ssl::stream<tcp_socket>;
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
using unix_socket = boost::asio::local::stream_protocol::socket;
using unix_ssl_socket = boost::asio::ssl::stream<unix_socket>;
#endif
// Stream names
template <class Stream>
constexpr const char* get_stream_name();
template <>
constexpr const char* get_stream_name<tcp_socket>()
{
return "tcp";
}
template <>
constexpr const char* get_stream_name<tcp_ssl_socket>()
{
return "tcp_ssl";
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
template <>
constexpr const char* get_stream_name<unix_socket>()
{
return "unix";
}
template <>
constexpr const char* get_stream_name<unix_ssl_socket>()
{
return "unix_ssl";
}
#endif
// Supports SSL (doesn't use the lib's type trait for test independance)
template <class Stream>
constexpr bool supports_ssl();
template <>
constexpr bool supports_ssl<tcp_socket>()
{
return false;
}
template <>
constexpr bool supports_ssl<tcp_ssl_socket>()
{
return true;
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
template <>
constexpr bool supports_ssl<unix_socket>()
{
return false;
}
template <>
constexpr bool supports_ssl<unix_ssl_socket>()
{
return true;
}
#endif
// is_unix_socket
template <class Stream>
constexpr bool is_unix_socket()
{
return false;
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
template <>
constexpr bool is_unix_socket<unix_socket>()
{
return true;
}
template <>
constexpr bool is_unix_socket<unix_ssl_socket>()
{
return true;
}
#endif
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_TCP_CONNECTION_FIXTURE_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_TCP_CONNECTION_FIXTURE_HPP
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/tcp.hpp>
#include <boost/assert/source_location.hpp>
#include "test_common/source_location.hpp"
namespace boost {
namespace mysql {
namespace test {
struct tcp_connection_fixture
{
tcp_connection conn;
tcp_connection_fixture();
~tcp_connection_fixture();
void connect(source_location loc = BOOST_MYSQL_CURRENT_LOCATION);
void connect(const handshake_params&, source_location loc = BOOST_MYSQL_CURRENT_LOCATION);
};
asio::ip::tcp::endpoint get_tcp_endpoint();
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,56 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_TCP_NETWORK_FIXTURE_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_TCP_NETWORK_FIXTURE_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/tcp.hpp>
#include <boost/asio/io_context.hpp>
#include "test_integration/common.hpp"
#include "test_integration/get_endpoint.hpp"
#include "test_integration/streams.hpp"
namespace boost {
namespace mysql {
namespace test {
struct tcp_network_fixture : network_fixture_base
{
boost::mysql::tcp_connection conn;
tcp_network_fixture() : conn(ctx.get_executor()) { conn.set_meta_mode(metadata_mode::full); }
~tcp_network_fixture()
{
try
{
error_code ec;
diagnostics diag;
conn.close(ec, diag);
}
catch (...)
{
}
}
void connect() { conn.connect(get_endpoint<tcp_socket>(), params); }
void start_transaction()
{
results result;
conn.execute("START TRANSACTION", result);
}
};
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -12,6 +12,7 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/assert/source_location.hpp>
#include <boost/config.hpp>
#include <boost/core/detail/string_view.hpp>
#include <boost/core/span.hpp>

View File

@ -1,115 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/asio/bind_executor.hpp>
#include "er_impl_common.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_integration/er_connection.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/streams.hpp"
using namespace boost::mysql::test;
namespace boost {
namespace mysql {
namespace test {
template <class Base>
class async_callback_base : public Base
{
protected:
using conn_type = typename Base::conn_type;
using base_type = Base;
template <class R, class... Args>
using pmem_t = void (conn_type::*)(Args..., diagnostics&, as_network_result<R>&&);
template <class R, class... Args>
network_result<R> fn_impl(pmem_t<R, Args...> p, Args... args)
{
// Get the object's I/O executor
auto ex = this->conn().get_executor();
// Create the initial result
auto res = create_initial_netresult<R>();
{
// Mark ourselves as initiating
initiation_guard guard;
// Invoke the initiation function
invoke_polyfill(
p,
this->conn(),
std::forward<Args>(args)...,
*res.diag,
as_network_result<R>(res, ex)
);
}
// Run
run_until_completion(ex);
// Done
return res;
}
public:
using Base::Base;
static constexpr const char* name() noexcept { return "async_callback"; }
};
template <class Stream>
class async_callback_connection : public async_callback_base<connection_base<Stream>>
{
using base_type = async_callback_base<connection_base<Stream>>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC()
};
class any_async_callback_connection : public async_callback_base<any_connection_base>
{
using base_type = async_callback_base<any_connection_base>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC_ANY()
};
template <class Stream>
void add_async_callback_variant(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
add_variant<async_callback_connection<Stream>>(output);
}
} // namespace test
} // namespace mysql
} // namespace boost
void boost::mysql::test::add_async_callback(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
// Spotcheck for both streams
add_async_callback_variant<tcp_socket>(output);
add_async_callback_variant<tcp_ssl_socket>(output);
add_variant_any<address_type::host_and_port, any_async_callback_connection>(output);
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (get_server_features().unix_sockets)
{
add_async_callback_variant<unix_socket>(output);
add_variant_any<address_type::unix_path, any_async_callback_connection>(output);
}
#endif
}

View File

@ -1,80 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/spawn.hpp>
#include <exception>
#include "er_impl_common.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_integration/streams.hpp"
// Coroutines test async without diagnostics overloads
namespace boost {
namespace mysql {
namespace test {
template <class Base>
class async_coroutine_base : public Base
{
protected:
using conn_type = typename Base::conn_type;
using base_type = Base;
template <class R, class... Args>
using pmem_t = R (conn_type::*)(Args..., boost::asio::yield_context&&);
template <class R, class... Args>
network_result<R> fn_impl(pmem_t<R, Args...> p, Args... args)
{
auto res = create_initial_netresult<R>(false);
boost::asio::spawn(
this->conn().get_executor(),
[&](boost::asio::yield_context yield) {
invoke_and_assign(res, p, this->conn(), std::forward<Args>(args)..., yield[res.err]);
},
&rethrow_on_failure
);
run_until_completion(this->conn().get_executor());
return res;
}
public:
using Base::Base;
static constexpr const char* name() noexcept { return "async_coroutines"; }
};
template <class Stream>
class async_coroutine_connection : public async_coroutine_base<connection_base<Stream>>
{
using base_type = async_coroutine_base<connection_base<Stream>>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC()
};
class any_async_coroutine_connection : public async_coroutine_base<any_connection_base>
{
using base_type = async_coroutine_base<any_connection_base>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC_ANY()
};
} // namespace test
} // namespace mysql
} // namespace boost
void boost::mysql::test::add_async_coroutines(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
add_variant<async_coroutine_connection<tcp_socket>>(output);
add_variant_any<address_type::host_and_port, any_async_coroutine_connection>(output);
}

View File

@ -1,124 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <exception>
#include "er_impl_common.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_integration/streams.hpp"
#ifdef BOOST_ASIO_HAS_CO_AWAIT
// C++20 coroutines test async with diagnostics overloads & deferred tokens
namespace boost {
namespace mysql {
namespace test {
template <class R>
using result_tuple = std::
conditional_t<std::is_same_v<R, void>, std::tuple<error_code>, std::tuple<error_code, R>>;
using token_t = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<>>;
template <class R>
void to_network_result(const std::tuple<error_code, R>& tup, network_result<R>& netresult)
{
netresult.err = std::get<0>(tup);
netresult.value = std::get<1>(tup);
}
void to_network_result(const std::tuple<error_code>& tup, network_result<void>& netresult)
{
netresult.err = std::get<0>(tup);
}
template <class Base>
class async_coroutinecpp20_base : public Base
{
protected:
using conn_type = typename Base::conn_type;
using base_type = Base;
template <class R, class... Args>
using pmem_t = boost::asio::awaitable<result_tuple<R>> (conn_type::*)(Args..., diagnostics&, token_t&&);
template <class R, class... Args>
network_result<R> fn_impl(pmem_t<R, Args...> p, Args... args)
{
auto res = create_initial_netresult<R>();
boost::asio::co_spawn(
this->conn().get_executor(),
[&]() -> boost::asio::awaitable<void> {
// Create the task
auto aw = (this->conn().*p)(std::forward<Args>(args)..., *res.diag, token_t());
// Verify that initiation didn't have side effects
BOOST_TEST(res.diag->server_message() == "diagnostics not cleared properly");
// Run the task
auto tup = co_await std::move(aw);
to_network_result(tup, res);
},
// Regular errors are handled via error_code's. This will just
// propagate unexpected ones.
&rethrow_on_failure
);
run_until_completion(this->conn().get_executor());
return res;
}
public:
using Base::Base;
static constexpr const char* name() noexcept { return "async_coroutinescpp20"; }
};
template <class Stream>
class async_coroutinecpp20_connection : public async_coroutinecpp20_base<connection_base<Stream>>
{
using base_type = async_coroutinecpp20_base<connection_base<Stream>>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC()
};
class any_async_coroutinecpp20_connection : public async_coroutinecpp20_base<any_connection_base>
{
using base_type = async_coroutinecpp20_base<any_connection_base>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_ASYNC_ANY()
};
} // namespace test
} // namespace mysql
} // namespace boost
void boost::mysql::test::add_async_coroutinescpp20(
std::vector<std::reference_wrapper<er_network_variant>>& output
)
{
add_variant<async_coroutinecpp20_connection<tcp_ssl_socket>>(output);
add_variant_any<address_type::host_and_port, any_async_coroutinecpp20_connection>(output);
}
#else
void boost::mysql::test::add_async_coroutinescpp20(std::vector<std::reference_wrapper<er_network_variant>>&)
{
}
#endif

View File

@ -1,448 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_INTEGRATION_SRC_ER_IMPL_COMMON_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_SRC_ER_IMPL_COMMON_HPP
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <cassert>
#include <string>
#include <type_traits>
#include "test_common/ci_server.hpp"
#include "test_common/network_result.hpp"
#include "test_integration/er_connection.hpp"
#include "test_integration/er_network_variant.hpp"
#include "test_integration/get_endpoint.hpp"
#include "test_integration/streams.hpp"
namespace boost {
namespace mysql {
namespace test {
// Variants
void add_sync_errc(std::vector<std::reference_wrapper<er_network_variant>>&);
void add_sync_exc(std::vector<std::reference_wrapper<er_network_variant>>&);
void add_async_callback(std::vector<std::reference_wrapper<er_network_variant>>&);
void add_async_coroutines(std::vector<std::reference_wrapper<er_network_variant>>&);
void add_async_coroutinescpp20(std::vector<std::reference_wrapper<er_network_variant>>&);
// Helpers
template <class Stream>
connection<Stream> create_connection_impl(
boost::asio::any_io_executor executor,
boost::asio::ssl::context& ssl_ctx,
std::true_type // is ssl stream
)
{
return connection<Stream>(executor, ssl_ctx);
}
template <class Stream>
connection<Stream> create_connection_impl(
boost::asio::any_io_executor executor,
boost::asio::ssl::context&,
std::false_type // is ssl stream
)
{
return connection<Stream>(executor);
}
template <class Stream>
connection<Stream> create_connection(
boost::asio::any_io_executor executor,
boost::asio::ssl::context& ssl_ctx
)
{
return create_connection_impl<Stream>(
executor,
ssl_ctx,
std::integral_constant<bool, supports_ssl<Stream>()>()
);
}
// Base for implementing er_connection
template <class Stream>
class connection_base : public er_connection
{
public:
using stream_type = Stream;
using conn_type = connection<Stream>;
using stmt_tuple = bound_statement_tuple<std::tuple<field_view, field_view>>;
using stmt_it = bound_statement_iterator_range<fv_list_it>;
connection_base(
boost::asio::any_io_executor executor,
boost::asio::ssl::context& ssl_ctx,
er_network_variant& var
)
: conn_(create_connection<Stream>(executor, ssl_ctx)), var_(var)
{
}
conn_type& conn() noexcept { return conn_; }
bool uses_ssl() const override { return conn_.uses_ssl(); }
bool is_open() const override { return conn_.stream().lowest_layer().is_open(); }
void set_metadata_mode(metadata_mode v) override { conn_.set_meta_mode(v); }
void physical_connect() override { conn_.stream().lowest_layer().connect(get_endpoint<Stream>()); }
void sync_close() noexcept override
{
try
{
conn_.close();
}
catch (...)
{
}
}
er_network_variant& variant() const override { return var_; }
private:
conn_type conn_;
er_network_variant& var_;
};
class any_connection_base : public er_connection
{
static any_connection_params make_ctor_params(asio::ssl::context& ctx) noexcept
{
any_connection_params res;
res.ssl_context = &ctx;
return res;
}
public:
using conn_type = any_connection;
using base_type = any_connection_base;
using stmt_tuple = bound_statement_tuple<std::tuple<field_view, field_view>>;
using stmt_it = bound_statement_iterator_range<fv_list_it>;
connect_params get_connect_params(const handshake_params& input) const noexcept
{
connect_params res;
if (addr_type_ == address_type::host_and_port)
res.server_address.emplace_host_and_port(get_hostname());
else if (addr_type_ == address_type::unix_path)
res.server_address.emplace_unix_path(default_unix_path);
res.username = input.username();
res.password = input.password();
res.database = input.database();
res.multi_queries = input.multi_queries();
return res;
}
any_connection_base(
boost::asio::any_io_executor executor,
boost::asio::ssl::context& ssl_ctx,
er_network_variant& var,
address_type addr
)
: conn_(executor, make_ctor_params(ssl_ctx)), var_(var), addr_type_(addr)
{
}
any_connection& conn() noexcept { return conn_; }
bool uses_ssl() const override { return conn_.uses_ssl(); }
bool is_open() const override { return true; } // TODO
void set_metadata_mode(metadata_mode v) override { conn_.set_meta_mode(v); }
void physical_connect() override final { assert(false); } // not allowed in v2
network_result<void> handshake(const handshake_params&) override final
{
return error_code(asio::error::operation_not_supported);
}
network_result<void> quit() override final { return error_code(asio::error::operation_not_supported); }
void sync_close() noexcept override
{
try
{
conn_.close();
}
catch (...)
{
}
}
er_network_variant& variant() const override { return var_; }
private:
any_connection conn_;
er_network_variant& var_;
address_type addr_type_;
};
// Macros to help implementing er_connection
#ifdef BOOST_MYSQL_CXX14
#define BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_CXX14(prefix) \
network_result<void> execute(string_view q, er_connection::static_results_t& result) override \
{ \
return this->template fn_impl<void, const string_view&, er_connection::static_results_t&>( \
&conn_type::prefix##execute, \
q, \
result \
); \
} \
network_result<void> start_execution(string_view q, er_connection::static_state_t& st) override \
{ \
return this->template fn_impl<void, const string_view&, er_connection::static_state_t&>( \
&conn_type::prefix##start_execution, \
q, \
st \
); \
} \
network_result<void> read_resultset_head(er_connection::static_state_t& st) override \
{ \
return this->template fn_impl<void, er_connection::static_state_t&>( \
&conn_type::prefix##read_resultset_head, \
st \
); \
} \
network_result<std::size_t> read_some_rows( \
er_connection::static_state_t& st, \
boost::span<row_multifield> storage \
) override \
{ \
return this \
->template fn_impl<std::size_t, er_connection::static_state_t&, boost::span<row_multifield>>( \
&conn_type::prefix##read_some_rows, \
st, \
storage \
); \
} \
network_result<std::size_t> read_some_rows( \
er_connection::static_state_t& st, \
boost::span<row_2fields> storage \
) override \
{ \
return this \
->template fn_impl<std::size_t, er_connection::static_state_t&, boost::span<row_2fields>>( \
&conn_type::prefix##read_some_rows, \
st, \
storage \
); \
}
#else
#define BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_CXX14(prefix)
#endif
#define BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_COMMON(prefix) \
using conn_type = typename base_type::conn_type; \
using base_type::base_type; \
network_result<statement> prepare_statement(string_view stmt_sql) override \
{ \
return this->template fn_impl<statement, string_view>( \
&conn_type::prefix##prepare_statement, \
stmt_sql \
); \
} \
network_result<void> execute(string_view query, results& result) override \
{ \
return this->template fn_impl<void, const string_view&, results&>( \
&conn_type::prefix##execute, \
query, \
result \
); \
} \
network_result<void> execute( \
bound_statement_tuple<std::tuple<field_view, field_view>> req, \
results& result \
) override \
{ \
return this->template fn_impl<void, const typename base_type::stmt_tuple&, results&>( \
&conn_type::prefix##execute, \
req, \
result \
); \
} \
network_result<void> execute(bound_statement_iterator_range<fv_list_it> req, results& result) override \
{ \
return this->template fn_impl<void, const typename base_type::stmt_it&, results&>( \
&conn_type::prefix##execute, \
req, \
result \
); \
} \
network_result<void> start_execution(string_view query, execution_state& st) override \
{ \
return this->template fn_impl<void, const string_view&, execution_state&>( \
&conn_type::prefix##start_execution, \
query, \
st \
); \
} \
network_result<void> start_execution( \
bound_statement_tuple<std::tuple<field_view, field_view>> req, \
execution_state& st \
) override \
{ \
return this->template fn_impl<void, const typename base_type::stmt_tuple&, execution_state&>( \
&conn_type::prefix##start_execution, \
req, \
st \
); \
} \
network_result<void> start_execution( \
bound_statement_iterator_range<fv_list_it> req, \
execution_state& st \
) override \
{ \
return this->template fn_impl<void, const typename base_type::stmt_it&, execution_state&>( \
&conn_type::prefix##start_execution, \
req, \
st \
); \
} \
network_result<void> close_statement(statement& stmt) override \
{ \
return this->template fn_impl<void, const statement&>(&conn_type::prefix##close_statement, stmt); \
} \
network_result<void> read_resultset_head(execution_state& st) override \
{ \
return this->template fn_impl<void, execution_state&>(&conn_type::prefix##read_resultset_head, st); \
} \
network_result<rows_view> read_some_rows(execution_state& st) override \
{ \
return this->template fn_impl<rows_view, execution_state&>(&conn_type::prefix##read_some_rows, st); \
} \
network_result<void> ping() override { return this->template fn_impl<void>(&conn_type::prefix##ping); } \
network_result<void> reset_connection() override \
{ \
return this->template fn_impl<void>(&conn_type::prefix##reset_connection); \
} \
network_result<void> close() override \
{ \
return this->template fn_impl<void>(&conn_type::prefix##close); \
} \
BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_CXX14(prefix)
#define BOOST_MYSQL_TEST_IMPLEMENT_GENERIC(prefix) \
network_result<void> connect(const handshake_params& params) override \
{ \
return this->template fn_impl< \
void, \
const typename Stream::lowest_layer_type::endpoint_type&, \
const handshake_params&>(&conn_type::prefix##connect, get_endpoint<Stream>(), params); \
} \
network_result<void> handshake(const handshake_params& params) override \
{ \
return this->template fn_impl<void, const handshake_params&>(&conn_type::prefix##handshake, params); \
} \
network_result<void> quit() override { return this->template fn_impl<void>(&conn_type::prefix##quit); } \
BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_COMMON(prefix)
#define BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_ANY(prefix) \
network_result<void> connect(const handshake_params& params) override \
{ \
return this->template fn_impl<void, const connect_params&>( \
&conn_type::prefix##connect, \
get_connect_params(params) \
); \
} \
BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_COMMON(prefix)
// Use these
#define BOOST_MYSQL_TEST_IMPLEMENT_SYNC() BOOST_MYSQL_TEST_IMPLEMENT_GENERIC()
#define BOOST_MYSQL_TEST_IMPLEMENT_ASYNC() BOOST_MYSQL_TEST_IMPLEMENT_GENERIC(async_)
#define BOOST_MYSQL_TEST_IMPLEMENT_SYNC_ANY() BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_ANY()
#define BOOST_MYSQL_TEST_IMPLEMENT_ASYNC_ANY() BOOST_MYSQL_TEST_IMPLEMENT_GENERIC_ANY(async_)
// Implementation for er_network_variant
template <class ErConnection>
class er_network_variant_impl : public er_network_variant
{
using stream_type = typename ErConnection::stream_type;
public:
bool supports_ssl() const override { return ::boost::mysql::test::supports_ssl<stream_type>(); }
bool is_unix_socket() const override { return ::boost::mysql::test::is_unix_socket<stream_type>(); }
bool supports_handshake() const override { return true; }
const char* stream_name() const override { return ::boost::mysql::test::get_stream_name<stream_type>(); }
const char* variant_name() const override { return ErConnection::name(); }
er_connection_ptr create_connection(boost::asio::any_io_executor ex, boost::asio::ssl::context& ssl_ctx)
override
{
return er_connection_ptr(new ErConnection(std::move(ex), ssl_ctx, *this));
}
};
template <address_type addr_type>
constexpr const char* stream_name_from_type() noexcept;
template <>
constexpr const char* stream_name_from_type<address_type::host_and_port>() noexcept
{
return "any_tcp";
}
template <>
constexpr const char* stream_name_from_type<address_type::unix_path>() noexcept
{
return "any_unix";
}
template <address_type addr_type, class ErConnection>
class er_network_variant_any_impl : public er_network_variant
{
public:
bool supports_ssl() const override { return addr_type == address_type::host_and_port; }
bool is_unix_socket() const override { return addr_type == address_type::unix_path; }
bool supports_handshake() const override { return false; }
const char* stream_name() const override { return stream_name_from_type<addr_type>(); }
const char* variant_name() const override { return ErConnection::name(); }
er_connection_ptr create_connection(boost::asio::any_io_executor ex, boost::asio::ssl::context& ssl_ctx)
override
{
return er_connection_ptr(new ErConnection(std::move(ex), ssl_ctx, *this, addr_type));
}
};
// Helpers
inline void rethrow_on_failure(std::exception_ptr ptr)
{
if (ptr)
{
std::rethrow_exception(ptr);
}
}
template <class ErConnection>
void add_variant(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
static er_network_variant_impl<ErConnection> obj;
output.push_back(obj);
}
template <address_type addr_type, class ErConnection>
void add_variant_any(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
static er_network_variant_any_impl<addr_type, ErConnection> obj;
output.push_back(obj);
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif

View File

@ -1,71 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <functional>
#include <unordered_map>
#include <vector>
#include "er_impl_common.hpp"
#include "test_integration/er_network_variant.hpp"
using namespace boost::mysql::test;
using boost::mysql::error_code;
static std::unordered_map<std::string, er_network_variant*> make_variants_map()
{
std::unordered_map<std::string, er_network_variant*> res;
for (er_network_variant& var : all_variants())
res[var.name()] = &var;
return res;
}
std::vector<std::reference_wrapper<er_network_variant>> boost::mysql::test::all_variants()
{
std::vector<std::reference_wrapper<er_network_variant>> res;
add_sync_errc(res);
add_sync_exc(res);
add_async_callback(res);
add_async_coroutines(res);
add_async_coroutinescpp20(res);
return res;
}
std::vector<std::reference_wrapper<er_network_variant>> boost::mysql::test::all_variants_with_handshake()
{
std::vector<std::reference_wrapper<er_network_variant>> res;
for (er_network_variant& var : all_variants())
{
if (var.supports_handshake())
res.push_back(var);
}
return res;
}
er_network_variant& boost::mysql::test::get_network_variant(string_view name)
{
static auto by_name = make_variants_map();
std::string name_str(name);
auto it = by_name.find(name_str);
if (it == by_name.end())
throw std::out_of_range("Unknown network variant: " + name_str);
return *it->second;
}
std::vector<std::reference_wrapper<er_network_variant>> boost::mysql::test::get_network_variants(
boost::span<const string_view> names
)
{
std::vector<std::reference_wrapper<er_network_variant>> res;
for (auto name : names)
res.push_back(get_network_variant(name));
return res;
}
std::ostream& boost::mysql::test::operator<<(std::ostream& os, const er_network_variant& value)
{
return os << value.stream_name() << "_" << value.variant_name();
}

View File

@ -1,44 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "test_integration/get_endpoint.hpp"
#include <boost/mysql/connection.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/test/unit_test.hpp>
#include <cstdlib>
#include "test_common/ci_server.hpp"
// Get the endpoint to use for TCP from an environment variable.
// Required as CI MySQL doesn't run on loocalhost
static boost::asio::ip::tcp::endpoint get_tcp_valid_endpoint()
{
boost::asio::io_context ctx;
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
auto results = resolver.resolve(boost::mysql::test::get_hostname(), boost::mysql::default_port_string);
return *results.begin();
}
boost::asio::ip::tcp::endpoint boost::mysql::test::endpoint_getter<boost::asio::ip::tcp>::operator()()
{
static auto res = get_tcp_valid_endpoint();
return res;
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
boost::asio::local::stream_protocol::endpoint boost::mysql::test::endpoint_getter<
boost::asio::local::stream_protocol>::operator()()
{
return boost::asio::local::stream_protocol::endpoint(default_unix_path);
}
#endif

View File

@ -5,13 +5,13 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "test_integration/metadata_validator.hpp"
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/test/unit_test.hpp>
#include "test_integration/metadata_validator.hpp"
using namespace boost::mysql::test;
#define MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(getter) \
@ -32,7 +32,8 @@ static struct flag_entry
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_zerofill),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_auto_increment),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(has_no_default_value),
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_set_to_now_on_update)};
MYSQL_TEST_FLAG_GETTER_NAME_ENTRY(is_set_to_now_on_update)
};
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<flag_entry>::iterator)
@ -90,3 +91,12 @@ void boost::mysql::test::validate_meta(
expected[i].validate(actual[i]);
}
}
void boost::mysql::test::validate_2fields_meta(metadata_collection_view fields, string_view table)
{
validate_meta(
fields,
{meta_validator(table, "id", column_type::int_),
meta_validator(table, "field_varchar", column_type::varchar)}
);
}

View File

@ -0,0 +1,123 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/ip/tcp.hpp>
#include <ostream>
#include <utility>
#include "test_integration/spotchecks_helpers.hpp"
using namespace boost::mysql::test;
// Network functions
#define BOOST_MYSQL_MAKE_NETFN(conn, netm, fn, i) \
(i == 0 ? netmakers::netm::sync_errc(&conn::fn) \
: i == 1 ? netmakers::netm::sync_exc(&conn::fn) \
: i == 2 ? netmakers::netm::async_diag(&conn::async_##fn) \
: netmakers::netm::async_nodiag(&conn::async_##fn))
static constexpr const char* fn_names[] = {"sync_errc", "sync_exc", "async_diag", "async_nodiag"};
std::vector<network_functions_connection> boost::mysql::test::network_functions_connection::all()
{
std::vector<network_functions_connection> res;
for (std::size_t i = 0; i < 4; ++i)
{
// connect_stream doesn't involve diagnostics
auto fn_connect_stream = (i == 0 || i == 1) ? netmakers::connect_stream::sync_errc_nodiag(
&asio::ip::tcp::socket::connect
)
: netmakers::connect_stream::async_nodiag(
&asio::ip::tcp::socket::async_connect
);
res.push_back({
fn_names[i],
BOOST_MYSQL_MAKE_NETFN(tcp_connection, prepare_statement, prepare_statement, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, execute_query, execute, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, execute_statement, execute, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, start_execution, start_execution, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, close_statement, close_statement, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, read_resultset_head, read_resultset_head, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, read_some_rows, read_some_rows, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, ping, ping, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, reset_connection, reset_connection, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, close, close, i),
#ifdef BOOST_MYSQL_CXX14
BOOST_MYSQL_MAKE_NETFN(tcp_connection, execute_static, execute, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, start_execution_static, start_execution, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, read_resultset_head_static, read_resultset_head, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, read_some_rows_static_1, read_some_rows, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, read_some_rows_static_2, read_some_rows, i),
#endif
std::move(fn_connect_stream),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, handshake, handshake, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, connect, connect, i),
BOOST_MYSQL_MAKE_NETFN(tcp_connection, quit, quit, i),
});
}
return res;
}
std::vector<network_functions_connection> boost::mysql::test::network_functions_connection::sync_and_async()
{
auto all_fns = all();
return {std::move(all_fns[0]), std::move(all_fns[2])};
}
std::ostream& boost::mysql::test::operator<<(std::ostream& os, const network_functions_connection& v)
{
return os << v.name;
}
std::vector<network_functions_any> boost::mysql::test::network_functions_any::all()
{
std::vector<network_functions_any> res;
for (std::size_t i = 0; i < 4; ++i)
{
res.push_back({
fn_names[i],
BOOST_MYSQL_MAKE_NETFN(any_connection, prepare_statement, prepare_statement, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, execute_query, execute, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, execute_statement, execute, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, start_execution, start_execution, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, close_statement, close_statement, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, read_resultset_head, read_resultset_head, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, read_some_rows, read_some_rows, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, ping, ping, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, reset_connection, reset_connection, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, close, close, i),
#ifdef BOOST_MYSQL_CXX14
BOOST_MYSQL_MAKE_NETFN(any_connection, execute_static, execute, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, start_execution_static, start_execution, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, read_resultset_head_static, read_resultset_head, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, read_some_rows_static_1, read_some_rows, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, read_some_rows_static_2, read_some_rows, i),
#endif
BOOST_MYSQL_MAKE_NETFN(any_connection, connect, connect, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, set_character_set, set_character_set, i),
BOOST_MYSQL_MAKE_NETFN(any_connection, run_pipeline, run_pipeline, i),
});
}
return res;
}
std::vector<network_functions_any> boost::mysql::test::network_functions_any::sync_and_async()
{
auto all_fns = all();
return {std::move(all_fns[0]), std::move(all_fns[2])};
}
std::ostream& boost::mysql::test::operator<<(std::ostream& os, const network_functions_any& v)
{
return os << v.name;
}

View File

@ -1,100 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include "er_impl_common.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/streams.hpp"
using namespace boost::mysql::test;
using boost::mysql::error_code;
// MSVC complains about passing empty tokens, which is valid C++
#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4003)
#endif
namespace boost {
namespace mysql {
namespace test {
template <class Base>
class sync_errc_base : public Base
{
protected:
using conn_type = typename Base::conn_type;
using base_type = Base;
// workaround for gcc5
template <class R, class... Args>
struct pmem
{
using type = R (conn_type::*)(Args..., error_code&, diagnostics&);
};
template <class R, class... Args>
network_result<R> fn_impl(typename pmem<R, Args...>::type p, Args... args)
{
auto res = create_initial_netresult<R>();
invoke_and_assign(res, p, this->conn(), std::forward<Args>(args)..., res.err, *res.diag);
return res;
}
public:
using Base::Base;
static constexpr const char* name() noexcept { return "sync_errc"; }
};
template <class Stream>
class sync_errc_connection : public sync_errc_base<connection_base<Stream>>
{
using base_type = sync_errc_base<connection_base<Stream>>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_SYNC()
};
class any_sync_errc_connection final : public sync_errc_base<any_connection_base>
{
using base_type = sync_errc_base<any_connection_base>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_SYNC_ANY()
};
template <class Stream>
void add_sync_errc_variant(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
add_variant<sync_errc_connection<Stream>>(output);
}
} // namespace test
} // namespace mysql
} // namespace boost
void boost::mysql::test::add_sync_errc(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
// Verify that all streams work
add_sync_errc_variant<tcp_socket>(output);
add_sync_errc_variant<tcp_ssl_socket>(output);
add_variant_any<address_type::host_and_port, any_sync_errc_connection>(output);
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (get_server_features().unix_sockets)
{
add_sync_errc_variant<unix_socket>(output);
add_sync_errc_variant<unix_ssl_socket>(output);
add_variant_any<address_type::unix_path, any_sync_errc_connection>(output);
}
#endif
}
#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

View File

@ -1,91 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include "er_impl_common.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_integration/streams.hpp"
using namespace boost::mysql::test;
// MSVC complains about passing empty tokens, which is valid C++
#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4003)
#endif
namespace boost {
namespace mysql {
namespace test {
template <class Base>
class sync_exc_base : public Base
{
protected:
using conn_type = typename Base::conn_type;
using base_type = Base;
template <class R, class... Args>
using pmem_t = R (conn_type::*)(Args...);
template <class R, class... Args>
network_result<R> fn_impl(pmem_t<R, Args...> p, Args... args)
{
network_result<R> res;
try
{
invoke_and_assign(res, p, this->conn(), std::forward<Args>(args)...);
}
catch (const boost::mysql::error_with_diagnostics& err)
{
res.err = err.code();
res.diag = err.get_diagnostics();
}
catch (const boost::system::system_error& err)
{
res.err = err.code();
}
return res;
}
public:
using Base::Base;
static constexpr const char* name() noexcept { return "sync_exc"; }
};
template <class Stream>
class sync_exc_connection : public sync_exc_base<connection_base<Stream>>
{
using base_type = sync_exc_base<connection_base<Stream>>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_SYNC()
};
class any_sync_exc_connection final : public sync_exc_base<any_connection_base>
{
using base_type = sync_exc_base<any_connection_base>;
public:
BOOST_MYSQL_TEST_IMPLEMENT_SYNC_ANY()
};
} // namespace test
} // namespace mysql
} // namespace boost
void boost::mysql::test::add_sync_exc(std::vector<std::reference_wrapper<er_network_variant>>& output)
{
// Spotcheck
add_variant<sync_exc_connection<tcp_socket>>(output);
add_variant_any<address_type::host_and_port, any_sync_exc_connection>(output);
}
#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

View File

@ -0,0 +1,105 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/defaults.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/assert/source_location.hpp>
#include "test_common/ci_server.hpp"
#include "test_common/network_result.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/tcp_connection_fixture.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
namespace asio = boost::asio;
static any_connection_params make_params(asio::ssl::context& ssl_ctx)
{
any_connection_params res;
res.ssl_context = &ssl_ctx;
return res;
}
// any_connection_fixture
any_connection_fixture::any_connection_fixture(any_connection_params params)
: conn(global_context_executor(), params)
{
conn.set_meta_mode(metadata_mode::full);
}
any_connection_fixture::any_connection_fixture(asio::ssl::context& ssl_ctx)
: any_connection_fixture(make_params(ssl_ctx))
{
}
any_connection_fixture::~any_connection_fixture() { conn.async_close(as_netresult).validate_no_error(); }
void any_connection_fixture::connect(const connect_params& params, boost::source_location loc)
{
conn.async_connect(params, as_netresult).validate_no_error(loc);
}
void any_connection_fixture::connect(boost::source_location loc)
{
connect(connect_params_builder().ssl(ssl_mode::disable).build(), loc);
}
void any_connection_fixture::start_transaction(boost::source_location loc)
{
results r;
conn.async_execute("START TRANSACTION", r, as_netresult).validate_no_error(loc);
}
// tcp_connection_fixture
static asio::ip::tcp::endpoint resolve_server_endpoint()
{
asio::io_context ctx;
asio::ip::tcp::resolver resolver(ctx);
auto results = resolver.resolve(get_hostname(), default_port_string);
return *results.begin();
}
tcp_connection_fixture::tcp_connection_fixture() : conn(global_context_executor())
{
conn.set_meta_mode(metadata_mode::full);
}
tcp_connection_fixture::~tcp_connection_fixture() { conn.async_close(as_netresult).validate_no_error(); }
void tcp_connection_fixture::connect(boost::source_location loc)
{
connect(connect_params_builder().build_hparams(), loc);
}
void tcp_connection_fixture::connect(const handshake_params& params, boost::source_location loc)
{
conn.async_connect(get_tcp_endpoint(), params, as_netresult).validate_no_error(loc);
}
asio::ip::tcp::endpoint boost::mysql::test::get_tcp_endpoint()
{
static auto res = resolve_server_endpoint();
return res;
}
// connect_params_builder
connect_params connect_params_builder::build()
{
connect_params res;
res.server_address = std::move(addr_);
res.username = res_.username();
res.password = res_.password();
res.database = res_.database();
res.multi_queries = res_.multi_queries();
res.ssl = res_.ssl();
res.connection_collation = res_.connection_collation();
return res;
}

View File

@ -7,7 +7,6 @@
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/execution_state.hpp>
@ -21,23 +20,18 @@
#include <boost/mysql/impl/internal/variant_stream.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/host_name_verification.hpp>
#include <boost/test/tree/decorator.hpp>
#include <boost/asio/local/basic_endpoint.hpp>
#include <boost/test/data/test_case.hpp>
#include <string>
#include "test_common/create_basic.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_common/netfun_maker.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/server_ca.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/spotchecks_helpers.hpp"
// Additional spotchecks for any_connection
@ -48,192 +42,72 @@ using boost::test_tools::per_element;
BOOST_AUTO_TEST_SUITE(test_any_connection)
using netmaker_connect = netfun_maker_mem<void, any_connection, const connect_params&>;
using netmaker_execute = netfun_maker_mem<void, any_connection, const string_view&, results&>;
// Don't validate executor info, since our I/O objects don't use tracker executors
const auto connect_fn = netmaker_connect::async_errinfo(&any_connection::async_connect);
const auto execute_fn = netmaker_execute::async_errinfo(&any_connection::async_execute);
// Passing no SSL context to the constructor and using SSL works.
// ssl_mode::require works
BOOST_AUTO_TEST_CASE(default_ssl_context)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Call the function
connect_fn(conn, default_connect_params(ssl_mode::require)).validate_no_error();
// uses_ssl reports the right value
BOOST_TEST(conn.uses_ssl());
}
// Passing a custom SSL context works
BOOST_AUTO_TEST_CASE(custom_ssl_context)
{
// Set up a SSL context
asio::ssl::context ssl_ctx(asio::ssl::context::tlsv12_client);
ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM));
ssl_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("bad.host.name"));
// Create the connection
asio::io_context ctx;
any_connection_params ctor_params;
ctor_params.ssl_context = &ssl_ctx;
any_connection conn(ctx, ctor_params);
// Certificate validation fails
auto result = connect_fn(conn, default_connect_params(ssl_mode::require));
BOOST_TEST(result.err.message().find("certificate verify failed") != std::string::npos);
}
// Disabling SSL works with TCP connections
BOOST_AUTO_TEST_CASE(tcp_ssl_mode_disable)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Call the function
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
// uses_ssl reports the right value
BOOST_TEST(!conn.uses_ssl());
}
// SSL mode enable woks with TCP connections
BOOST_AUTO_TEST_CASE(tcp_ssl_mode_enable)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Call the function
connect_fn(conn, default_connect_params(ssl_mode::enable)).validate_no_error();
// All our CIs support SSL
BOOST_TEST(conn.uses_ssl());
}
// any_connection can be used with UNIX sockets
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
// UNIX connections never use SSL
BOOST_TEST_DECORATOR(*run_if(&server_features::unix_sockets))
BOOST_AUTO_TEST_CASE(unix_ssl)
BOOST_FIXTURE_TEST_CASE(unix_sockets, any_connection_fixture)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Connect params
auto params = default_connect_params(ssl_mode::require);
params.server_address.emplace_unix_path(default_unix_path);
// Call the function
connect_fn(conn, params).validate_no_error();
// SSL is not enabled even if we specified require, since there's
// no point in using SSL with UNIX sockets
// Connect
connect(connect_params_builder().set_unix().build());
BOOST_TEST(!conn.uses_ssl());
}
#endif
// Spotcheck: users can log-in using the caching_sha2_password auth plugin
BOOST_TEST_DECORATOR(*run_if(&server_features::sha256))
BOOST_AUTO_TEST_CASE(tcp_caching_sha2_password)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// We can prepare statements
auto stmt = conn.async_prepare_statement("SELECT ?", as_netresult).get();
BOOST_TEST(stmt.num_params() == 1u);
// Connect params
auto params = default_connect_params(ssl_mode::require);
params.username = "csha2p_user";
params.password = "csha2p_password";
// Call the function
connect_fn(conn, params).validate_no_error();
BOOST_TEST(conn.uses_ssl());
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
// Users can log-in using the caching_sha2_password auth plugin
// even if they're using UNIX sockets.
BOOST_TEST_DECORATOR(*run_if(&server_features::sha256, &server_features::unix_sockets))
BOOST_AUTO_TEST_CASE(unix_caching_sha2_password)
{
// Setup
boost::asio::io_context ctx;
any_connection conn(ctx);
any_connection root_conn(ctx);
// Clear the sha256 cache. This forces us send the password using plain text.
// TODO: we could create a dedicated user - this can make the test more reliable
auto root_params = default_connect_params();
// We can execute queries
results r;
root_params.username = "root";
root_params.password = "";
root_conn.connect(root_params);
root_conn.execute("FLUSH PRIVILEGES", r);
conn.async_execute("SELECT 'abc'", r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, "abc"), per_element());
// Connect params
auto params = default_connect_params(ssl_mode::require);
params.server_address.emplace_unix_path(default_unix_path);
params.username = "csha2p_user";
params.password = "csha2p_password";
// We can execute statements
conn.async_execute(stmt.bind(42), r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, 42), per_element());
// Call the function
connect_fn(conn, params).validate_no_error();
BOOST_TEST(!conn.uses_ssl());
// We can get errors
conn.async_execute("SELECT * FROM bad_table", r, as_netresult)
.validate_error(
common_server_errc::er_no_such_table,
"Table 'boost_mysql_integtests.bad_table' doesn't exist"
);
// We can terminate the connection
conn.async_close(as_netresult).validate_no_error();
}
#endif
network_result<void> create_net_result()
{
return network_result<void>(
common_server_errc::er_aborting_connection,
create_server_diag("diagnostics not cleared")
);
}
// Backslash escapes
BOOST_AUTO_TEST_CASE(backslash_escapes)
BOOST_FIXTURE_TEST_CASE(backslash_escapes, any_connection_fixture)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Backslash escapes enabled by default
BOOST_TEST(conn.backslash_escapes());
// Connect doesn't change the value
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
conn.async_connect(connect_params_builder().disable_ssl().build(), as_netresult).validate_no_error();
BOOST_TEST(conn.backslash_escapes());
BOOST_TEST(conn.format_opts()->backslash_escapes);
// Setting the SQL mode to NO_BACKSLASH_ESCAPES updates the value
results r;
execute_fn(conn, "SET sql_mode = 'NO_BACKSLASH_ESCAPES'", r).validate_no_error();
conn.async_execute("SET sql_mode = 'NO_BACKSLASH_ESCAPES'", r, as_netresult).validate_no_error();
BOOST_TEST(!conn.backslash_escapes());
BOOST_TEST(!conn.format_opts()->backslash_escapes);
// Executing a different statement doesn't change the value
execute_fn(conn, "SELECT 1", r).validate_no_error();
conn.async_execute("SELECT 1", r, as_netresult).validate_no_error();
BOOST_TEST(!conn.backslash_escapes());
BOOST_TEST(!conn.format_opts()->backslash_escapes);
// Clearing the SQL mode updates the value
execute_fn(conn, "SET sql_mode = ''", r).validate_no_error();
conn.async_execute("SET sql_mode = ''", r, as_netresult).validate_no_error();
BOOST_TEST(conn.backslash_escapes());
BOOST_TEST(conn.format_opts()->backslash_escapes);
// Reconnecting clears the value
execute_fn(conn, "SET sql_mode = 'NO_BACKSLASH_ESCAPES'", r).validate_no_error();
conn.async_execute("SET sql_mode = 'NO_BACKSLASH_ESCAPES'", r, as_netresult).validate_no_error();
BOOST_TEST(!conn.backslash_escapes());
BOOST_TEST(!conn.format_opts()->backslash_escapes);
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
conn.async_connect(connect_params_builder().disable_ssl().build(), as_netresult).validate_no_error();
BOOST_TEST(conn.backslash_escapes());
BOOST_TEST(conn.format_opts()->backslash_escapes);
}
@ -242,94 +116,67 @@ BOOST_AUTO_TEST_CASE(backslash_escapes)
BOOST_AUTO_TEST_CASE(max_buffer_size)
{
// Create the connection
boost::asio::io_context ctx;
any_connection_params params;
params.initial_buffer_size = 512u;
params.max_buffer_size = 512u;
any_connection conn(ctx, params);
any_connection_fixture fix(params);
// Connect
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
fix.conn.async_connect(connect_params_builder().disable_ssl().build(), as_netresult).validate_no_error();
// Reading and writing almost 512 bytes works
results r;
auto q = format_sql(conn.format_opts().value(), "SELECT {}", std::string(450, 'a'));
execute_fn(conn, q, r).validate_no_error();
auto q = format_sql(fix.conn.format_opts().value(), "SELECT {}", std::string(450, 'a'));
fix.conn.async_execute(q, r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, std::string(450, 'a')), per_element());
// Trying to write more than 512 bytes fails
q = format_sql(conn.format_opts().value(), "SELECT LENGTH({})", std::string(512, 'a'));
execute_fn(conn, q, r).validate_error_exact(client_errc::max_buffer_size_exceeded);
q = format_sql(fix.conn.format_opts().value(), "SELECT LENGTH({})", std::string(512, 'a'));
fix.conn.async_execute(q, r, as_netresult).validate_error(client_errc::max_buffer_size_exceeded);
// Trying to read more than 512 bytes fails
execute_fn(conn, "SELECT REPEAT('a', 512)", r)
.validate_error_exact(client_errc::max_buffer_size_exceeded);
fix.conn.async_execute("SELECT REPEAT('a', 512)", r, as_netresult)
.validate_error(client_errc::max_buffer_size_exceeded);
}
BOOST_AUTO_TEST_CASE(default_max_buffer_size_success)
BOOST_FIXTURE_TEST_CASE(default_max_buffer_size_success, any_connection_fixture)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Connect
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
conn.async_connect(connect_params_builder().disable_ssl().build(), as_netresult).validate_no_error();
// Reading almost max_buffer_size works
execution_state st;
conn.start_execution("SELECT 1, REPEAT('a', 0x3f00000)", st);
auto rws = conn.read_some_rows(st);
conn.async_start_execution("SELECT 1, REPEAT('a', 0x3f00000)", st, as_netresult).validate_no_error();
auto rws = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rws.at(0).at(1).as_string().size() == 0x3f00000u);
}
BOOST_AUTO_TEST_CASE(default_max_buffer_size_error)
BOOST_FIXTURE_TEST_CASE(default_max_buffer_size_error, any_connection_fixture)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Connect
connect_fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
conn.async_connect(connect_params_builder().disable_ssl().build(), as_netresult).validate_no_error();
// Trying to read more than max_buffer_size bytes fails
results r;
execute_fn(conn, "SELECT 1, REPEAT('a', 0x4000000)", r)
.validate_error_exact(client_errc::max_buffer_size_exceeded);
conn.async_execute("SELECT 1, REPEAT('a', 0x4000000)", r, as_netresult)
.validate_error(client_errc::max_buffer_size_exceeded);
}
BOOST_AUTO_TEST_CASE(naggle_disabled)
BOOST_DATA_TEST_CASE(naggle_disabled, network_functions_any::sync_and_async())
{
struct
{
string_view name;
netmaker_connect::signature fn;
} test_cases[] = {
{"sync", netmaker_connect::sync_errc(&any_connection::connect)},
{"async", connect_fn },
};
// Setup
netfn_fixture_any fix(sample);
for (const auto& tc : test_cases)
{
BOOST_TEST_CONTEXT(tc.name)
{
// Create the connection
boost::asio::io_context ctx;
any_connection conn(ctx);
// Connect
fix.net.connect(fix.conn, connect_params_builder().disable_ssl().build()).validate_no_error();
// Connect
tc.fn(conn, default_connect_params(ssl_mode::disable)).validate_no_error();
// Naggle's algorithm was disabled
asio::ip::tcp::no_delay opt;
static_cast<detail::engine_impl<detail::variant_stream>&>(
detail::access::get_impl(conn).get_engine()
)
.stream()
.tcp_socket()
.get_option(opt);
BOOST_TEST(opt.value() == true);
}
}
// Naggle's algorithm was disabled
asio::ip::tcp::no_delay opt;
static_cast<detail::engine_impl<detail::variant_stream>&>(detail::access::get_impl(fix.conn).get_engine())
.stream()
.tcp_socket()
.get_option(opt);
BOOST_TEST(opt.value() == true);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -9,37 +9,49 @@
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/mysql_collations.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/assert/source_location.hpp>
#include <boost/test/unit_test.hpp>
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_common/source_location.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::test_tools::per_element;
namespace asio = boost::asio;
BOOST_AUTO_TEST_SUITE(test_character_set_tracking)
static void validate_db_charset(any_connection& conn, string_view expected_charset)
static void validate_db_charset(
any_connection& conn,
string_view expected_charset,
boost::source_location loc = BOOST_MYSQL_CURRENT_LOCATION
)
{
// Issue the query
constexpr const char*
query = "SELECT @@character_set_client, @@character_set_connection, @@character_set_results";
results r;
conn.execute("SELECT @@character_set_client, @@character_set_connection, @@character_set_results", r);
const auto rw = r.rows().at(0);
BOOST_TEST(rw.at(0).as_string() == expected_charset);
BOOST_TEST(rw.at(1).as_string() == expected_charset);
BOOST_TEST(rw.at(2).as_string() == expected_charset);
conn.async_execute(query, r, as_netresult).validate_no_error(loc);
// Check
BOOST_TEST_CONTEXT("Called from " << loc)
{
const auto expected = makerows(3, expected_charset, expected_charset, expected_charset);
BOOST_TEST(r.rows() == expected, per_element());
}
}
BOOST_AUTO_TEST_CASE(charset_lifecycle)
BOOST_FIXTURE_TEST_CASE(charset_lifecycle, any_connection_fixture)
{
// Setup
asio::io_context ctx;
any_connection conn(ctx);
// Non-connected connections have an unknown charset
BOOST_TEST(conn.current_character_set().error() == client_errc::unknown_character_set);
BOOST_TEST(conn.format_opts().error() == client_errc::unknown_character_set);
@ -47,46 +59,40 @@ BOOST_AUTO_TEST_CASE(charset_lifecycle)
// Connect with the default character set uses utf8mb4, both in the client
// and in the server. This double-checks that all supported servers support the
// collation we use by default.
conn.connect(default_connect_params(ssl_mode::disable));
connect();
BOOST_TEST(conn.current_character_set()->name == "utf8mb4");
BOOST_TEST(conn.format_opts()->charset.name == "utf8mb4");
validate_db_charset(conn, "utf8mb4");
// Using set_character_set updates the character set everywhere
character_set greek_charset{"greek", ascii_charset.next_char};
conn.set_character_set(greek_charset);
conn.async_set_character_set(greek_charset, as_netresult).validate_no_error();
BOOST_TEST(conn.current_character_set()->name == "greek");
BOOST_TEST(conn.format_opts()->charset.name == "greek");
validate_db_charset(conn, "greek");
// Using reset_connection wipes out client-side character set information
conn.reset_connection();
conn.async_reset_connection(as_netresult).validate_no_error();
BOOST_TEST(conn.current_character_set().error() == client_errc::unknown_character_set);
BOOST_TEST(conn.format_opts().error() == client_errc::unknown_character_set);
// We can use set_character_set to recover from this
conn.set_character_set(greek_charset);
conn.async_set_character_set(greek_charset, as_netresult).validate_no_error();
BOOST_TEST(conn.current_character_set()->name == "greek");
BOOST_TEST(conn.format_opts()->charset.name == "greek");
validate_db_charset(conn, "greek");
}
BOOST_AUTO_TEST_CASE(connect_with_unknown_collation)
BOOST_FIXTURE_TEST_CASE(connect_with_unknown_collation, any_connection_fixture)
{
// Setup
asio::io_context ctx;
any_connection conn(ctx);
// Connect with a collation that some servers may not support, or that we don't know of
auto params = default_connect_params(ssl_mode::disable);
params.connection_collation = mysql_collations::utf8mb4_0900_ai_ci; // not supported by MariaDB, triggers
// fallback
conn.connect(params);
// utf8mb4_0900_ai_ci is not supported by MariaDB, triggers fallback
connect(connect_params_builder().collation(mysql_collations::utf8mb4_0900_ai_ci).build());
BOOST_TEST(conn.current_character_set().error() == client_errc::unknown_character_set);
BOOST_TEST(conn.format_opts().error() == client_errc::unknown_character_set);
// Explicitly setting the character set solves the issue
conn.set_character_set(ascii_charset);
conn.async_set_character_set(ascii_charset, as_netresult).validate_no_error();
BOOST_TEST(conn.current_character_set()->name == "ascii");
validate_db_charset(conn, "ascii");
}

View File

@ -6,137 +6,143 @@
//
#include <boost/mysql/connection.hpp>
#include <boost/mysql/date.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/tcp.hpp>
#include <boost/test/unit_test_suite.hpp>
#include "test_integration/common.hpp"
#include "test_integration/tcp_network_fixture.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/metadata_validator.hpp"
using namespace boost::mysql::test;
using boost::mysql::results;
using namespace boost::mysql;
using boost::test_tools::per_element;
namespace {
BOOST_AUTO_TEST_SUITE(test_crud)
// Other SELECT statements are already covered
BOOST_FIXTURE_TEST_CASE(query_empty_select, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(query_empty_select, any_connection_fixture)
{
connect();
// Issue query
results result;
conn.execute("SELECT * FROM empty_table", result);
conn.async_execute("SELECT * FROM empty_table", result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST(result.size() == 1u);
validate_2fields_meta(result.meta(), "empty_table");
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 0u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "");
}
BOOST_FIXTURE_TEST_CASE(query_empty_select_multifn, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(query_empty_select_multifn, any_connection_fixture)
{
connect();
// Issue query
boost::mysql::execution_state st;
conn.start_execution("SELECT * FROM empty_table", st);
execution_state st;
conn.async_start_execution("SELECT * FROM empty_table", st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
validate_2fields_meta(st.meta(), "empty_table");
// Read eof
auto rv = conn.read_some_rows(st);
BOOST_TEST(rv.empty());
auto rv = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rv == rows_view(), per_element());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
BOOST_TEST(st.info() == "");
}
BOOST_FIXTURE_TEST_CASE(query_insert, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(query_insert, any_connection_fixture)
{
connect();
start_transaction();
// Issue query
constexpr const char*
query = "INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')";
results result;
conn.execute("INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", result);
conn.async_execute(query, result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST(result.size() == 1u);
BOOST_TEST(result.meta().empty());
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 1u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() > 0u);
BOOST_TEST(result.info() == "");
// Verify insertion took place
conn.execute("SELECT COUNT(*) FROM inserts_table", result);
BOOST_TEST(result.rows().at(0).at(0).as_int64() == 1);
conn.async_execute("SELECT COUNT(*) FROM inserts_table", result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 1), per_element());
}
BOOST_FIXTURE_TEST_CASE(query_update, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(query_update, any_connection_fixture)
{
connect();
start_transaction();
// Issue the query
results result;
conn.execute("UPDATE updates_table SET field_int = field_int+10", result);
conn.async_execute("UPDATE updates_table SET field_int = field_int+10", result, as_netresult)
.validate_no_error();
// Validate results
BOOST_TEST(result.size() == 1u);
BOOST_TEST(result.meta().empty());
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 2u); // there are 3 rows, but 1 has field_int = NULL
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "Rows matched: 3 Changed: 2 Warnings: 0");
// Validate it took effect
conn.execute("SELECT field_int FROM updates_table WHERE field_varchar = 'f0'", result);
BOOST_TEST(result.rows().at(0).at(0).as_int64() == 52); // initial value was 42
conn.async_execute("SELECT field_int FROM updates_table WHERE field_varchar = 'f0'", result, as_netresult)
.validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 52), per_element()); // initial value was 42
}
BOOST_FIXTURE_TEST_CASE(query_delete, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(query_delete, any_connection_fixture)
{
connect();
start_transaction();
// Issue the query
results result;
conn.execute("DELETE FROM updates_table", result);
conn.async_execute("DELETE FROM updates_table", result, as_netresult).validate_no_error();
// Validate results
BOOST_TEST(result.size() == 1u);
BOOST_TEST(result.meta().empty());
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 3u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "");
// Validate it took effect
conn.execute("SELECT COUNT(*) FROM updates_table", result);
BOOST_TEST(result.rows().at(0).at(0).as_int64() == 0);
conn.async_execute("SELECT COUNT(*) FROM updates_table", result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 0), per_element());
}
BOOST_FIXTURE_TEST_CASE(statement_update, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(statement_update, any_connection_fixture)
{
connect();
start_transaction();
// Prepare the statement
auto stmt = conn.prepare_statement("UPDATE updates_table SET field_int = ? WHERE field_varchar = ?");
constexpr const char* sql = "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?";
auto stmt = conn.async_prepare_statement(sql, as_netresult).get();
BOOST_TEST(stmt.num_params() == 2u);
// Execute it
@ -144,43 +150,45 @@ BOOST_FIXTURE_TEST_CASE(statement_update, tcp_network_fixture)
conn.execute(stmt.bind(200, "f0"), result);
BOOST_TEST(result.size() == 1u);
BOOST_TEST(result.meta().empty());
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 1u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "Rows matched: 1 Changed: 1 Warnings: 0");
// Verify that it took effect
conn.execute("SELECT field_int FROM updates_table WHERE field_varchar = 'f0'", result);
BOOST_TEST(result.rows().at(0).at(0).as_int64() == 200);
conn.async_execute("SELECT field_int FROM updates_table WHERE field_varchar = 'f0'", result, as_netresult)
.validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 200), per_element());
// Close the statement
conn.close_statement(stmt);
conn.async_close_statement(stmt, as_netresult).validate_no_error();
}
BOOST_FIXTURE_TEST_CASE(statement_delete, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(statement_delete, any_connection_fixture)
{
connect();
start_transaction();
// Prepare the statement
auto stmt = conn.prepare_statement("DELETE FROM updates_table WHERE field_varchar = ?");
constexpr const char* sql = "DELETE FROM updates_table WHERE field_varchar = ?";
auto stmt = conn.async_prepare_statement(sql, as_netresult).get();
BOOST_TEST(stmt.num_params() == 1u);
// Execute it
results result;
conn.execute(stmt.bind("f0"), result);
conn.async_execute(stmt.bind("f0"), result, as_netresult).validate_no_error();
BOOST_TEST(result.size() == 1u);
BOOST_TEST(result.meta().empty());
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 1u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "");
// Validate it took effect
conn.execute("SELECT COUNT(*) FROM updates_table", result);
BOOST_TEST(result.rows().at(0).at(0).as_int64() == 2);
conn.async_execute("SELECT COUNT(*) FROM updates_table", result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 2), per_element());
}
BOOST_AUTO_TEST_SUITE_END() // test_crud

View File

@ -23,6 +23,7 @@
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/static_results.hpp>
#include <boost/mysql/tcp.hpp>
@ -37,20 +38,22 @@
#include <boost/optional/optional.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <ostream>
#include <stdint.h>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/metadata_validator.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/tcp_network_fixture.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
@ -84,22 +87,23 @@ const flagsvec no_flags{};
constexpr format_options opts{utf8mb4_charset, true};
struct database_types_fixture : tcp_network_fixture
struct database_types_fixture : any_connection_fixture
{
database_types_fixture()
{
// Connect
params.set_multi_queries(true);
connect();
connect(connect_params_builder().multi_queries(true).build());
// Sets the time_zone to a well known value, so we can deterministically read TIMESTAMPs
// Sets also sql_mode to allow invalid dates
results result;
conn.execute(
"SET session time_zone = '+02:00'; "
"SET session sql_mode = 'ALLOW_INVALID_DATES'",
result
);
conn.async_execute(
"SET session time_zone = '+02:00'; "
"SET session sql_mode = 'ALLOW_INVALID_DATES'",
result,
as_netresult
)
.validate_no_error();
}
};
@ -123,7 +127,7 @@ struct table_base
}
virtual ~table_base() {}
virtual void select_static(tcp_connection& conn) = 0;
virtual void select_static(any_connection& conn) = 0;
void add_meta(
std::string field,
@ -136,95 +140,30 @@ struct table_base
metas.emplace_back(name, std::move(field), type, std::move(flags), decimals, std::move(ignore_flags));
}
void validate_rows(boost::mysql::rows_view actual_matrix) const
void validate_rows(rows_view actual) const
{
// The matrix size is correct
BOOST_TEST_REQUIRE(metas.size() == actual_matrix.num_columns());
// Sort the expected rows as the database retrieves it
std::vector<row> expected{rws.begin(), rws.end()};
std::sort(expected.begin(), expected.end(), [](row_view r1, row_view r2) {
return r1.at(0).as_string() < r2.at(0).as_string();
});
// Build a map with the received rows, by ID
std::unordered_map<std::string, boost::mysql::row_view> actual;
for (const auto& row : actual_matrix)
actual[std::string(row.at(0).as_string())] = row;
// Verify that all expected rows are there and match
for (const auto& expected_row : rws)
{
auto id = expected_row.at(0).as_string();
BOOST_TEST_CONTEXT("row_id=" << id)
{
auto it = actual.find(std::string(id));
if (it == actual.end())
{
BOOST_TEST(false, "Row not found in the actual table");
}
else
{
BOOST_TEST(
expected_row == it->second,
"\nlhs: " << expected_row << "\nrhs: " << it->second
);
actual.erase(it);
}
}
}
// Verify that there are no additional rows
for (const auto& additional_row : actual)
{
BOOST_TEST_CONTEXT("row_id=" << additional_row.first)
{
BOOST_TEST(false, "Row was found in the table but not declared in database_types");
}
}
// Compare
BOOST_TEST(actual == expected, boost::test_tools::per_element());
}
std::string select_sql() const { return format_sql(opts, "SELECT * FROM {:i} ORDER BY id", name); }
std::string insert_sql_stmt() const
{
format_context ctx(opts);
format_sql_to(ctx, "INSERT INTO {:i} VALUES (", name);
for (std::size_t i = 0; i < metas.size(); ++i)
{
if (i == 0)
ctx.append_raw("?");
else
ctx.append_raw(", ?");
}
ctx.append_raw(")");
return std::move(ctx).get().value();
auto format_fn = [](const meta_validator&, format_context_base& ctx) { ctx.append_raw("?"); };
return format_sql(opts, "INSERT INTO {:i} VALUES ({})", name, sequence(metas, format_fn));
}
std::string insert_sql() const
{
format_context ctx(opts);
format_sql_to(ctx, "INSERT INTO {:i} VALUES ", name);
bool is_first_row = true;
for (const auto& r : rws)
{
// Comma separator between rows
if (!is_first_row)
ctx.append_raw(", ");
is_first_row = false;
// Actual row
ctx.append_raw("(");
bool is_first_field = true;
for (const field_view fv : r)
{
// Comma separator between fields
if (!is_first_field)
ctx.append_raw(", ");
is_first_field = false;
// Actual field
ctx.append_value(fv);
}
ctx.append_raw(")");
}
return std::move(ctx).get().value();
auto format_fn = [](row_view r, format_context_base& ctx) { format_sql_to(ctx, "({})", r); };
return format_sql(opts, "INSERT INTO {:i} VALUES {}", name, sequence(rws, format_fn));
}
std::string delete_sql() const { return format_sql(opts, "DELETE FROM {:i}", name); }
@ -255,53 +194,24 @@ public:
}
#ifdef BOOST_MYSQL_CXX14
void select_static(tcp_connection& conn) override
void select_static(any_connection& conn) override
{
// Execute the query
static_results<StaticRow> result;
conn.execute(select_sql(), result);
conn.async_execute(select_sql(), result, as_netresult).validate_no_error();
// Validate metadata
validate_meta(result.meta(), metas);
// Validate the rows
// Build a map with the received rows, by ID
std::unordered_map<std::string, StaticRow> actual;
for (const auto& row : result.rows())
actual[row.id] = row;
// Verify that all expected rows are there and match
for (const auto& expected_row : static_rows_)
{
BOOST_TEST_CONTEXT("row_id=" << expected_row.id)
{
auto it = actual.find(expected_row.id);
if (it == actual.end())
{
BOOST_TEST(false, "Row not found in the actual table");
}
else
{
BOOST_TEST(
expected_row == it->second,
"\nlhs: " << expected_row << "\nrhs: " << it->second
);
actual.erase(it);
}
}
}
// Verify that there are no additional rows
for (const auto& additional_row : actual)
{
BOOST_TEST_CONTEXT("row_id=" << additional_row.first)
{
BOOST_TEST(false, "Row was found in the table but not declared in database_types");
}
}
std::vector<StaticRow> expected{static_rows_};
std::sort(expected.begin(), expected.end(), [](const StaticRow& r1, const StaticRow& r2) {
return r1.id < r2.id;
});
BOOST_TEST(result.rows() == expected, boost::test_tools::per_element());
}
#else
void select_static(tcp_connection&) override {}
void select_static(any_connection&) override {}
#endif
};
@ -1038,7 +948,7 @@ BOOST_FIXTURE_TEST_CASE(query_read, database_types_fixture)
{
// Execute the query
results result;
conn.execute(table->select_sql(), result);
conn.async_execute(table->select_sql(), result, as_netresult).validate_no_error();
// Validate the received contents
validate_meta(result.meta(), table->metas);
@ -1057,13 +967,13 @@ BOOST_FIXTURE_TEST_CASE(sql_format_query_write, database_types_fixture)
{
// Remove all contents from the table
results result;
conn.execute(table->delete_sql(), result);
conn.async_execute(table->delete_sql(), result, as_netresult).validate_no_error();
// Insert all the contents again
conn.execute(table->insert_sql(), result);
conn.async_execute(table->insert_sql(), result, as_netresult).validate_no_error();
// Query them again and verify the insertion was okay
conn.execute(table->select_sql(), result);
conn.async_execute(table->select_sql(), result, as_netresult).validate_no_error();
validate_meta(result.meta(), table->metas);
table->validate_rows(result.rows());
}
@ -1077,11 +987,11 @@ BOOST_FIXTURE_TEST_CASE(statement_read, database_types_fixture)
BOOST_TEST_CONTEXT(table->name)
{
// Prepare the statement
auto stmt = conn.prepare_statement(table->select_sql());
auto stmt = conn.async_prepare_statement(table->select_sql(), as_netresult).get();
// Execute it with the provided parameters
results result;
conn.execute(stmt.bind(), result);
conn.async_execute(stmt.bind(), result, as_netresult).validate_no_error();
// Validate the received contents
validate_meta(result.meta(), table->metas);
@ -1099,21 +1009,22 @@ BOOST_FIXTURE_TEST_CASE(statement_write, database_types_fixture)
BOOST_TEST_CONTEXT(table->name)
{
// Prepare the statements
auto insert_stmt = conn.prepare_statement(table->insert_sql_stmt());
auto query_stmt = conn.prepare_statement(table->select_sql());
auto insert_stmt = conn.async_prepare_statement(table->insert_sql_stmt(), as_netresult).get();
auto query_stmt = conn.async_prepare_statement(table->select_sql(), as_netresult).get();
// Remove all contents from the table
results result;
conn.execute(table->delete_sql(), result);
conn.async_execute(table->delete_sql(), result, as_netresult).validate_no_error();
// Insert all the contents again
for (const auto& row : table->rws)
{
conn.execute(insert_stmt.bind(row.begin(), row.end()), result);
conn.async_execute(insert_stmt.bind(row.begin(), row.end()), result, as_netresult)
.validate_no_error();
}
// Query them again and verify the insertion was okay
conn.execute(query_stmt.bind(), result);
conn.async_execute(query_stmt.bind(), result, as_netresult).validate_no_error();
validate_meta(result.meta(), table->metas);
table->validate_rows(result.rows());
}

View File

@ -5,57 +5,46 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_categories.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/mariadb_server_errc.hpp>
#include <boost/mysql/mysql_server_errc.hpp>
#include <boost/mysql/results.hpp>
#include "test_common/create_diagnostics.hpp"
#include "test_common/network_result.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/tcp_network_fixture.hpp"
using namespace boost::mysql::test;
using boost::mysql::diagnostics;
using boost::mysql::error_code;
using boost::mysql::results;
using namespace boost::mysql;
namespace {
BOOST_AUTO_TEST_SUITE(test_db_specific)
BOOST_TEST_DECORATOR(*run_if(&server_features::regex_error_codes))
BOOST_FIXTURE_TEST_CASE(mysql_specific_error_code, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(mysql_specific_error_code, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
results result;
// This is reported as a common, less descriptive error in MySQL5 and MariaDB
conn.execute("select * from one_row_table where field_varchar regexp '(('", result, ec, diag);
error_code expected_ec(
boost::mysql::mysql_server_errc::er_regexp_mismatched_paren,
boost::mysql::get_mysql_server_category()
);
BOOST_TEST(ec == expected_ec);
error_code expected_ec(mysql_server_errc::er_regexp_mismatched_paren, get_mysql_server_category());
conn.async_execute("select * from one_row_table where field_varchar regexp '(('", result, as_netresult)
.validate_error(expected_ec, create_server_diag("Mismatched parenthesis in regular expression."));
}
BOOST_TEST_DECORATOR(*run_if(&server_features::dup_query_error_codes))
BOOST_FIXTURE_TEST_CASE(mariadb_specific_error_code, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(mariadb_specific_error_code, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
results result;
// This is reported as a common error in MySQL5 and MySQL8
conn.execute("WITH abc AS (SELECT 1), abc as (SELECT 2) SELECT * FROM abc", result, ec, diag);
error_code expected_ec(
boost::mysql::mariadb_server_errc::er_dup_query_name,
boost::mysql::get_mariadb_server_category()
);
BOOST_TEST(ec == expected_ec);
error_code expected_ec(mariadb_server_errc::er_dup_query_name, get_mariadb_server_category());
conn.async_execute("WITH abc AS (SELECT 1), abc as (SELECT 2) SELECT * FROM abc", result, as_netresult)
.validate_error(expected_ec, create_server_diag("Duplicate query name `abc` in WITH clause"));
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,124 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/results.hpp>
#include <boost/test/unit_test.hpp>
#include <forward_list>
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_integration/any_connection_fixture.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::test_tools::per_element;
// Cover all possible execution requests for execute() and async_execute()
namespace {
BOOST_AUTO_TEST_SUITE(test_execution_requests)
BOOST_FIXTURE_TEST_CASE(query, any_connection_fixture)
{
// Setup
connect();
results r;
execution_state st;
// execute
conn.async_execute("SELECT 1", r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, 1), per_element());
// start execution
conn.async_start_execution("SELECT 1", st, as_netresult).validate_no_error();
auto rws = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rws == makerows(1, 1), per_element());
}
BOOST_FIXTURE_TEST_CASE(stmt_tuple, any_connection_fixture)
{
// Setup
connect();
results r;
execution_state st;
auto stmt = conn.async_prepare_statement("SELECT ?", as_netresult).get();
BOOST_TEST_REQUIRE(stmt.num_params() == 1u);
// execute
conn.async_execute(stmt.bind("42"), r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, "42"), per_element());
// start execution
conn.async_start_execution(stmt.bind(90), st, as_netresult).validate_no_error();
auto rws = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rws == makerows(1, 90), per_element());
}
BOOST_FIXTURE_TEST_CASE(stmt_tuple_error, any_connection_fixture)
{
// Setup
connect();
results r;
execution_state st;
auto stmt = conn.async_prepare_statement("SELECT ?", as_netresult).get();
BOOST_TEST_REQUIRE(stmt.num_params() == 1u);
// execute
conn.async_execute(stmt.bind("42", 200), r, as_netresult).validate_error(client_errc::wrong_num_params);
// start execution
conn.async_start_execution(stmt.bind(), st, as_netresult).validate_error(client_errc::wrong_num_params);
}
BOOST_FIXTURE_TEST_CASE(stmt_range, any_connection_fixture)
{
// Setup
connect();
results r;
execution_state st;
std::forward_list<field_view> params{field_view(42), field_view("abc")};
auto stmt = conn.async_prepare_statement("SELECT ?, ?", as_netresult).get();
BOOST_TEST_REQUIRE(stmt.num_params() == 2u);
// execute
conn.async_execute(stmt.bind(params.begin(), params.end()), r, as_netresult).validate_no_error();
BOOST_TEST(r.rows() == makerows(2, 42, "abc"), per_element());
// start execution
conn.async_start_execution(stmt.bind(params.begin(), params.end()), st, as_netresult).validate_no_error();
auto rws = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rws == makerows(2, 42, "abc"), per_element());
}
BOOST_FIXTURE_TEST_CASE(stmt_range_error, any_connection_fixture)
{
// Setup
connect();
results r;
execution_state st;
std::forward_list<field_view> params{field_view(42)};
auto stmt = conn.async_prepare_statement("SELECT ?, ?", as_netresult).get();
BOOST_TEST_REQUIRE(stmt.num_params() == 2u);
// execute
conn.async_execute(stmt.bind(params.begin(), params.end()), r, as_netresult)
.validate_error(client_errc::wrong_num_params);
// start execution
conn.async_start_execution(stmt.bind(params.begin(), params.end()), st, as_netresult)
.validate_error(client_errc::wrong_num_params);
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace

File diff suppressed because it is too large Load Diff

View File

@ -6,40 +6,39 @@
//
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/results.hpp>
#include <boost/test/unit_test.hpp>
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_common/validate_string_contains.hpp"
#include "test_integration/common.hpp"
#include "test_integration/tcp_network_fixture.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/metadata_validator.hpp"
#include "test_integration/tcp_connection_fixture.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
using boost::test_tools::per_element;
namespace {
BOOST_AUTO_TEST_SUITE(test_multi_queries)
BOOST_FIXTURE_TEST_CASE(empty_results, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(empty_results, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
// Setup
constexpr const char* query =
"INSERT INTO inserts_table (field_varchar) VALUES ('abc');"
"INSERT INTO inserts_table (field_varchar) VALUES ('def');"
"DELETE FROM updates_table";
connect(connect_params_builder().disable_ssl().multi_queries(true).build());
start_transaction();
// Run the query
results result;
conn.execute(
R"%(
INSERT INTO inserts_table (field_varchar) VALUES ('abc');
INSERT INTO inserts_table (field_varchar) VALUES ('def');
DELETE FROM updates_table
)%",
result
);
conn.async_execute(query, result, as_netresult).validate_no_error();
// Validate results
BOOST_TEST_REQUIRE(result.size() == 3u);
@ -69,21 +68,19 @@ BOOST_FIXTURE_TEST_CASE(empty_results, tcp_network_fixture)
BOOST_TEST(!result[2].is_out_params());
}
BOOST_FIXTURE_TEST_CASE(data_results, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(data_results, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
// Setup
connect(connect_params_builder().disable_ssl().multi_queries(true).build());
start_transaction();
constexpr const char* query =
"SELECT * FROM one_row_table;"
"SELECT * FROM empty_table;"
"DELETE FROM updates_table";
// Execute
results result;
conn.execute(
R"%(
SELECT * FROM one_row_table;
SELECT * FROM empty_table;
DELETE FROM updates_table
)%",
result
);
conn.async_execute(query, result, as_netresult).validate_no_error();
// Validate results
BOOST_TEST_REQUIRE(result.size() == 3u);
@ -113,19 +110,47 @@ BOOST_FIXTURE_TEST_CASE(data_results, tcp_network_fixture)
BOOST_TEST(!result[2].is_out_params());
}
BOOST_FIXTURE_TEST_CASE(error_not_enabled, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(error_not_enabled, any_connection_fixture)
{
connect();
// Setup
connect(connect_params_builder().disable_ssl().build());
// Execute fails
results result;
error_code err;
diagnostics diag;
conn.execute("SELECT 1; SELECT 2", result, err, diag);
BOOST_TEST(err == common_server_errc::er_parse_error);
validate_string_contains(diag.server_message(), {"you have an error in your sql syntax"});
conn.async_execute("SELECT 1; SELECT 2", result, as_netresult)
.validate_error_contains(
common_server_errc::er_parse_error,
{"you have an error in your sql syntax"}
);
}
BOOST_AUTO_TEST_SUITE_END() // test_crud
// Spotcheck: old connection can do multi-queries
BOOST_FIXTURE_TEST_CASE(tcp_connection_enable, tcp_connection_fixture)
{
connect(connect_params_builder().multi_queries(true).build_hparams());
// Execute succeeds
results result;
conn.async_execute("SELECT 1; SELECT 2", result, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(result.size() == 2u);
BOOST_TEST(result.at(0).rows() == makerows(1, 1), per_element());
BOOST_TEST(result.at(1).rows() == makerows(1, 2), per_element());
}
BOOST_FIXTURE_TEST_CASE(tcp_connection_disabled, tcp_connection_fixture)
{
// Setup
connect();
// Execute succeeds
results result;
conn.async_execute("SELECT 1; SELECT 2", result, as_netresult)
.validate_error_contains(
common_server_errc::er_parse_error,
{"you have an error in your sql syntax"}
);
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace

View File

@ -5,26 +5,20 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/test/unit_test.hpp>
#include <vector>
#include "test_common/create_basic.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_common/netfun_helpers.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/any_connection_fixture.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
@ -35,17 +29,10 @@ namespace {
BOOST_AUTO_TEST_SUITE(test_pipeline)
struct fixture
{
asio::io_context ctx;
any_connection conn{ctx};
fixture() { conn.connect(default_connect_params(ssl_mode::disable)); }
};
BOOST_FIXTURE_TEST_CASE(success, fixture)
BOOST_FIXTURE_TEST_CASE(success, any_connection_fixture)
{
// Setup
connect();
pipeline_request req;
std::vector<stage_response> res;
@ -55,9 +42,7 @@ BOOST_FIXTURE_TEST_CASE(success, fixture)
.add_prepare_statement("SELECT * FROM one_row_table WHERE id = ?");
// Run it
auto result = create_initial_netresult<void>();
conn.run_pipeline(req, res, result.err, *result.diag);
result.validate_no_error();
conn.async_run_pipeline(req, res, as_netresult).validate_no_error();
// Check results
BOOST_TEST_REQUIRE(res.size() == 3u);
@ -78,9 +63,7 @@ BOOST_FIXTURE_TEST_CASE(success, fixture)
.add_close_statement(stmt);
// Run it
result = create_initial_netresult<void>();
conn.run_pipeline(req, res, result.err, *result.diag);
result.validate_no_error();
conn.async_run_pipeline(req, res, as_netresult).validate_no_error();
// Check results
BOOST_TEST_REQUIRE(res.size() == 4u);
@ -96,9 +79,10 @@ BOOST_FIXTURE_TEST_CASE(success, fixture)
BOOST_TEST(!res[3].has_statement());
}
BOOST_FIXTURE_TEST_CASE(errors, fixture)
BOOST_FIXTURE_TEST_CASE(errors, any_connection_fixture)
{
// Setup
connect();
pipeline_request req;
std::vector<stage_response> res;
@ -111,12 +95,11 @@ BOOST_FIXTURE_TEST_CASE(errors, fixture)
.add_execute("SELECT 'abc'"); // OK
// Run it. The result of the operation is the first encountered error
auto result = create_initial_netresult<void>();
conn.run_pipeline(req, res, result.err, *result.diag);
result.validate_error_exact(
common_server_errc::er_no_such_table,
"Table 'boost_mysql_integtests.bad_table' doesn't exist"
);
conn.async_run_pipeline(req, res, as_netresult)
.validate_error(
common_server_errc::er_no_such_table,
"Table 'boost_mysql_integtests.bad_table' doesn't exist"
);
// Check results
BOOST_TEST_REQUIRE(res.size() == 6u);

View File

@ -6,140 +6,133 @@
//
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/tcp.hpp>
#include <boost/test/unit_test.hpp>
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/tcp_network_fixture.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/metadata_validator.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
using boost::test_tools::per_element;
namespace {
BOOST_AUTO_TEST_SUITE(test_prepared_statements)
BOOST_FIXTURE_TEST_CASE(multiple_executions, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(multiple_executions, any_connection_fixture)
{
connect();
results result;
// Prepare a statement
auto stmt = conn.prepare_statement("SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?");
constexpr const char* sql = "SELECT * FROM two_rows_table WHERE id = ? OR field_varchar = ?";
auto stmt = conn.async_prepare_statement(sql, as_netresult).get();
// Execute it. Only one row will be returned (because of the id)
results result;
conn.execute(stmt.bind(1, "non_existent"), result);
conn.async_execute(stmt.bind(1, "non_existent"), result, as_netresult).validate_no_error();
validate_2fields_meta(result.meta(), "two_rows_table");
BOOST_TEST_REQUIRE(result.rows().size() == 1u);
BOOST_TEST((result.rows()[0] == makerow(1, "f0")));
BOOST_TEST(result.rows() == makerows(2, 1, "f0"), per_element());
// Execute it again, but with different values. This time, two rows are returned
conn.execute(stmt.bind(1, "f1"), result);
conn.async_execute(stmt.bind(1, "f1"), result, as_netresult).validate_no_error();
validate_2fields_meta(result.meta(), "two_rows_table");
BOOST_TEST_REQUIRE(result.rows().size() == 2u);
BOOST_TEST((result.rows()[0] == makerow(1, "f0")));
BOOST_TEST((result.rows()[1] == makerow(2, "f1")));
BOOST_TEST(result.rows() == makerows(2, 1, "f0", 2, "f1"), per_element());
// Close it
conn.close_statement(stmt);
conn.async_close_statement(stmt, as_netresult).validate_no_error();
}
BOOST_FIXTURE_TEST_CASE(multiple_statements, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(multiple_statements, any_connection_fixture)
{
connect();
start_transaction();
results result;
// Prepare an update and a select
results result;
auto stmt_update = conn.prepare_statement("UPDATE updates_table SET field_int = ? WHERE field_varchar = ?"
);
auto stmt_select = conn.prepare_statement("SELECT field_int FROM updates_table WHERE field_varchar = ?");
constexpr const char* update_sql = "UPDATE updates_table SET field_int = ? WHERE field_varchar = ?";
constexpr const char* select_sql = "SELECT field_int FROM updates_table WHERE field_varchar = ?";
auto stmt_update = conn.async_prepare_statement(update_sql, as_netresult).get();
auto stmt_select = conn.async_prepare_statement(select_sql, as_netresult).get();
BOOST_TEST(stmt_update.num_params() == 2u);
BOOST_TEST(stmt_select.num_params() == 1u);
BOOST_TEST(stmt_update.id() != stmt_select.id());
// Execute update
conn.execute(stmt_update.bind(210, "f0"), result);
conn.async_execute(stmt_update.bind(210, "f0"), result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.meta().size() == 0u);
BOOST_TEST(result.affected_rows() == 1u);
// Execute select
conn.execute(stmt_select.bind("f0"), result);
BOOST_TEST(result.rows().size() == 1u);
BOOST_TEST(result.rows()[0] == makerow(210));
conn.async_execute(stmt_select.bind("f0"), result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 210), per_element());
// Execute update again
conn.execute(stmt_update.bind(220, "f0"), result);
conn.async_execute(stmt_update.bind(220, "f0"), result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.meta().size() == 0u);
BOOST_TEST(result.affected_rows() == 1u);
// Update no longer needed, close it
conn.close_statement(stmt_update);
conn.async_close_statement(stmt_update, as_netresult).validate_no_error();
// Execute select again
conn.execute(stmt_select.bind("f0"), result);
BOOST_TEST(result.rows().size() == 1u);
BOOST_TEST(result.rows()[0] == makerow(220));
conn.async_execute(stmt_select.bind("f0"), result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, 220), per_element());
// Close select
conn.close_statement(stmt_select);
conn.async_close_statement(stmt_select, as_netresult).validate_no_error();
}
BOOST_FIXTURE_TEST_CASE(statement_without_params, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(statement_without_params, any_connection_fixture)
{
connect();
// Prepare the statement
auto stmt = conn.prepare_statement("SELECT * FROM empty_table");
auto stmt = conn.async_prepare_statement("SELECT * FROM empty_table", as_netresult).get();
BOOST_TEST(stmt.valid());
BOOST_TEST(stmt.num_params() == 0u);
// Execute doesn't error
results result;
conn.execute(stmt.bind(), result);
conn.async_execute(stmt.bind(), result, as_netresult).validate_no_error();
validate_2fields_meta(result.meta(), "empty_table");
BOOST_TEST(result.rows().size() == 0u);
BOOST_TEST(result.rows() == rows(), per_element());
}
BOOST_FIXTURE_TEST_CASE(wrong_num_params, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(wrong_num_params, any_connection_fixture)
{
connect();
// Prepare the statement
auto stmt = conn.prepare_statement("SELECT * FROM empty_table");
auto stmt = conn.async_prepare_statement("SELECT * FROM empty_table", as_netresult).get();
BOOST_TEST(stmt.valid());
BOOST_TEST(stmt.num_params() == 0u);
// Execute fails appropriately
error_code ec;
diagnostics diag;
results result;
conn.execute(stmt.bind(42), result, ec, diag);
BOOST_TEST(ec == client_errc::wrong_num_params);
conn.async_execute(stmt.bind(42), result, as_netresult).validate_error(client_errc::wrong_num_params);
}
// Note: multifn query is already covered in spotchecks
BOOST_FIXTURE_TEST_CASE(multifn, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(multifn, any_connection_fixture)
{
connect();
// Prepare the statement
auto stmt = conn.prepare_statement("SELECT * FROM three_rows_table");
auto stmt = conn.async_prepare_statement("SELECT * FROM three_rows_table", as_netresult).get();
// Execute it
execution_state st;
conn.start_execution(stmt.bind(), st);
conn.async_start_execution(stmt.bind(), st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
// We don't know how many rows there will be in each batch,
@ -149,23 +142,28 @@ BOOST_FIXTURE_TEST_CASE(multifn, tcp_network_fixture)
while (st.should_read_rows() && call_count <= 4)
{
++call_count;
for (row_view rv : conn.read_some_rows(st))
for (row_view rv : conn.async_read_some_rows(st, as_netresult).get())
all_rows.emplace_back(rv);
}
// Verify rows
BOOST_TEST_REQUIRE(all_rows.size() == 3u);
BOOST_TEST(all_rows[0] == makerow(1, "f0"));
BOOST_TEST(all_rows[1] == makerow(2, "f1"));
BOOST_TEST(all_rows[2] == makerow(3, "f2"));
BOOST_TEST(all_rows == makerows(2, 1, "f0", 2, "f1", 3, "f2"), per_element());
// Verify eof
validate_eof(st);
BOOST_TEST_REQUIRE(st.complete());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
BOOST_TEST(st.info() == "");
// Reading again does nothing
auto rows = conn.read_some_rows(st);
BOOST_TEST(rows.empty());
validate_eof(st);
auto rws = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rws == rows(), per_element());
BOOST_TEST_REQUIRE(st.complete());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
BOOST_TEST(st.info() == "");
}
BOOST_AUTO_TEST_SUITE_END() // test_prepared_statements

View File

@ -5,271 +5,219 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/throw_on_error.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/experimental/cancellation_condition.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/core/span.hpp>
#include <boost/test/data/monomorphic.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include "test_common/netfun_maker.hpp"
#include "test_integration/common.hpp"
#include "test_integration/er_network_variant.hpp"
#include "test_integration/network_samples.hpp"
#include "test_integration/run_stackful_coro.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/server_features.hpp"
#include "test_integration/spotchecks_helpers.hpp"
#include "test_integration/tcp_connection_fixture.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
using boost::asio::deferred;
using boost::asio::experimental::make_parallel_group;
using boost::asio::experimental::wait_for_one;
namespace asio = boost::asio;
using boost::test_tools::per_element;
namespace {
auto samples_with_reconnection = network_samples([]() {
const string_view variant_names[] = {
"tcp_sync_errc",
"tcp_async_callback",
"any_tcp_sync_errc",
"any_tcp_async_callback",
};
auto res = get_network_variants(variant_names);
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (get_server_features().unix_sockets)
{
res.push_back(get_network_variant("any_unix_sync_errc"));
}
#endif
return res;
});
auto samples_any = network_samples([]() {
const string_view variant_names[] = {
"any_tcp_sync_errc",
"any_tcp_async_callback",
};
auto res = get_network_variants(variant_names);
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (get_server_features().unix_sockets)
{
res.push_back(get_network_variant("any_unix_sync_errc"));
}
#endif
return res;
});
BOOST_AUTO_TEST_SUITE(test_reconnect)
struct reconnect_fixture : network_fixture
{
void do_query_ok()
{
results result;
conn->execute("SELECT * FROM empty_table", result).get();
BOOST_TEST(result.rows().empty());
}
};
auto connection_samples = network_functions_connection::sync_and_async();
auto any_samples = network_functions_any::sync_and_async();
auto any_samples_grid = boost::unit_test::data::make(any_samples) *
boost::unit_test::data::make({ssl_mode::disable, ssl_mode::require});
BOOST_DATA_TEST_CASE_F(reconnect_fixture, reconnect_after_close, samples_with_reconnection)
// Old connection can reconnect after close if the stream is not SSL
BOOST_DATA_TEST_CASE(reconnect_after_close_connection, connection_samples)
{
setup(sample);
netfn_fixture_connection fix(sample);
// Connect and use the connection
connect();
do_query_ok();
results r;
fix.net.connect(fix.conn, get_tcp_endpoint(), connect_params_builder().build_hparams())
.validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
// Close
conn->close().validate_no_error();
fix.net.close(fix.conn).validate_no_error();
// Reopen and use the connection normally
connect();
do_query_ok();
fix.net.connect(fix.conn, get_tcp_endpoint(), connect_params_builder().build_hparams())
.validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
}
BOOST_DATA_TEST_CASE_F(reconnect_fixture, reconnect_after_handshake_error, samples_with_reconnection)
// any_connection can reconnect after close, even if the stream uses ssl
BOOST_DATA_TEST_CASE(reconnect_after_close_any, any_samples_grid, p0, p1)
{
setup(sample);
// Error during server handshake
params.set_database("bad_database");
conn->connect(params).validate_error(
common_server_errc::er_dbaccess_denied_error,
{"database", "bad_database"}
);
// Reopen with correct parameters and use the connection normally
params.set_database("boost_mysql_integtests");
connect();
do_query_ok();
}
BOOST_DATA_TEST_CASE_F(reconnect_fixture, reconnect_while_connected, samples_any)
{
setup(sample);
netfn_fixture_any fix(p0);
ssl_mode mode = p1;
// Connect and use the connection
connect();
do_query_ok();
results r;
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).build()).validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
// Close
fix.net.close(fix.conn).validate_no_error();
// Reopen and use the connection normally
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).build()).validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
}
// Old connection can reconnect after handshake failure if the stream is not SSL
BOOST_DATA_TEST_CASE(reconnect_after_handshake_error_connection, connection_samples)
{
netfn_fixture_connection fix(sample);
// Error during server handshake
fix.net.connect(fix.conn, get_tcp_endpoint(), connect_params_builder().database("bad_db").build_hparams())
.validate_error(
common_server_errc::er_dbaccess_denied_error,
"Access denied for user 'integ_user'@'%' to database 'bad_db'"
);
// Reopen with correct parameters and use the connection normally
results r;
fix.net.connect(fix.conn, get_tcp_endpoint(), connect_params_builder().build_hparams())
.validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
}
// any_connection can reconnect after a handshake failure, even if SSL is used
BOOST_DATA_TEST_CASE(reconnect_after_handshake_error_any, any_samples_grid, p0, p1)
{
netfn_fixture_any fix(p0);
ssl_mode mode = p1;
// Error during server handshake
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).database("bad_db").build())
.validate_error(
common_server_errc::er_dbaccess_denied_error,
"Access denied for user 'integ_user'@'%' to database 'bad_db'"
);
// Reopen with correct parameters and use the connection normally
results r;
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).build()).validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
}
// any_connection can reconnect while it's connected
BOOST_DATA_TEST_CASE(reconnect_while_connected, any_samples_grid, p0, p1)
{
netfn_fixture_any fix(p0);
ssl_mode mode = p1;
// Connect and use the connection
results r;
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).build()).validate_no_error();
fix.net.execute_query(fix.conn, "SELECT * FROM empty_table", r).validate_no_error();
// We can safely connect again
params.set_username("root");
params.set_password("");
fix.net.connect(fix.conn, connect_params_builder().ssl(mode).credentials("root", "").build())
.validate_no_error();
// We've logged in as root. May be root@% or root@localhost
fix.net.execute_query(fix.conn, "SELECT SUBSTRING_INDEX(CURRENT_USER(), '@', 1)", r).validate_no_error();
BOOST_TEST(r.rows() == makerows(1, "root"), per_element());
// Close succeeds
fix.net.close(fix.conn).validate_no_error();
}
BOOST_FIXTURE_TEST_CASE(reconnect_after_cancel, any_connection_fixture)
{
// Setup
results r;
connect();
// We've logged in as root
results r;
conn->execute("SELECT CURRENT_USER()", r).validate_no_error();
BOOST_TEST(r.rows().at(0).at(0).as_string().starts_with("root"));
// Kick an operation that ends up cancelled
asio::cancellation_signal sig;
auto netres = conn.async_execute("DO SLEEP(2)", r, as_netresult_t{sig.slot()});
// Return to the event loop and emit the signal
asio::post(asio::bind_executor(global_context_executor(), [&]() {
// Emit the signal
sig.emit(asio::cancellation_type::terminal);
}));
// Wait for the operation to finish
std::move(netres).validate_error(asio::error::operation_aborted);
// We can connect again and use the connection
connect();
conn.async_execute("SELECT 42", r, as_netresult).validate_no_error();
}
// parallel_group doesn't work with this macro. See https://github.com/chriskohlhoff/asio/issues/1398
#ifndef BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT
BOOST_AUTO_TEST_CASE(reconnect_after_cancel)
// any_connection can change the stream type used by successive connect calls
BOOST_AUTO_TEST_CASE(change_stream_type)
{
run_stackful_coro([](boost::asio::yield_context yield) {
// Setup
auto connect_prms = default_connect_params();
any_connection conn(yield.get_executor());
results r;
boost::mysql::error_code ec;
boost::mysql::diagnostics diag;
// Helpers
const auto tcp_params = connect_params_builder().ssl(ssl_mode::disable).build();
const auto tcp_ssl_params = connect_params_builder().ssl(ssl_mode::require).build();
const auto unix_params = connect_params_builder().set_unix().build();
// Connect
conn.async_connect(connect_prms, diag, yield[ec]);
boost::mysql::throw_on_error(ec, diag);
// Kick an operation that ends up cancelled
auto wait_result = make_parallel_group(
conn.async_execute("DO SLEEP(2)", r, deferred),
boost::asio::post(yield.get_executor(), deferred)
)
.async_wait(wait_for_one(), yield);
// Verify this was the case
BOOST_TEST(std::get<0>(wait_result)[1] == 0u); // post completed first
BOOST_TEST(std::get<1>(wait_result) == boost::asio::error::operation_aborted);
// We can connect again
conn.async_connect(connect_prms, diag, yield[ec]);
boost::mysql::throw_on_error(ec, diag);
});
}
#endif
// any_connection can change the stream type used by successive connect calls.
// We need to split this test in two (TCP and UNIX), so UNIX cases don't run on Windows.
struct change_stream_type_fixture : network_fixture_base
{
// Functions, to run sync and async with the same code
using netmaker_connect = netfun_maker_mem<void, any_connection, const connect_params&>;
using netmaker_ping = netfun_maker_mem<void, any_connection>;
struct functions_t
{
netmaker_connect::signature connect;
netmaker_ping::signature ping;
};
// A test case sample
// Samples
struct test_case_t
{
const char* name;
functions_t fns;
string_view name;
const network_functions_any& fns;
connect_params first_params;
connect_params second_params;
};
// Runs the actual test
void run(boost::span<const test_case_t> test_cases)
std::vector<test_case_t> test_cases{
{"sync_tcp_tcpssl", any_samples[0], tcp_params, tcp_ssl_params},
{"async_tcp_tcpssl", any_samples[1], tcp_params, tcp_ssl_params},
{"async_tcpssl_tcp", any_samples[1], tcp_ssl_params, tcp_params },
};
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (get_server_features().unix_sockets)
{
for (const auto& tc : test_cases)
test_cases.push_back({"sync_unix_tcpssl", any_samples[0], unix_params, tcp_ssl_params});
test_cases.push_back({"async_unix_tcpssl", any_samples[1], unix_params, tcp_ssl_params});
test_cases.push_back({"async_tcpssl_unix", any_samples[1], tcp_ssl_params, unix_params});
test_cases.push_back({"async_tcp_unix", any_samples[1], tcp_params, unix_params});
}
#endif
// Actual test
for (const auto& tc : test_cases)
{
BOOST_TEST_CONTEXT(tc.name)
{
BOOST_TEST_CONTEXT(tc.name)
{
any_connection conn{ctx.get_executor()};
// Setup
netfn_fixture_any fix(tc.fns);
// Connect with the first stream type
tc.fns.connect(conn, tc.first_params).validate_no_error();
tc.fns.ping(conn).validate_no_error();
// Connect with the first stream type
fix.net.connect(fix.conn, tc.first_params).validate_no_error();
fix.net.ping(fix.conn).validate_no_error();
// Connect with the second stream type
tc.fns.connect(conn, tc.second_params).validate_no_error();
tc.fns.ping(conn).validate_no_error();
}
// Connect with the second stream type
fix.net.connect(fix.conn, tc.second_params).validate_no_error();
fix.net.ping(fix.conn).validate_no_error();
}
}
functions_t sync_fns{
netmaker_connect::sync_errc(&any_connection::connect),
netmaker_ping::sync_errc(&any_connection::ping),
};
functions_t async_fns{
netmaker_connect::async_errinfo(&any_connection::async_connect),
netmaker_ping::async_errinfo(&any_connection::async_ping),
};
connect_params tcp_params;
connect_params tcp_ssl_params;
change_stream_type_fixture()
: tcp_params(default_connect_params(ssl_mode::disable)),
tcp_ssl_params(default_connect_params(ssl_mode::require))
{
}
};
// TCP cases. Note that some sync cases are not included, to save testing time
BOOST_FIXTURE_TEST_CASE(change_stream_type_tcp, change_stream_type_fixture)
{
test_case_t test_cases[] = {
{"sync_tcp_tcpssl", sync_fns, tcp_params, tcp_ssl_params},
{"async_tcp_tcpssl", async_fns, tcp_params, tcp_ssl_params},
{"async_tcpssl_tcp", async_fns, tcp_ssl_params, tcp_params },
};
run(test_cases);
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
// UNIX cases. Note that some sync cases are not included, to save testing time
BOOST_TEST_DECORATOR(*run_if(&server_features::unix_sockets))
BOOST_FIXTURE_TEST_CASE(change_stream_type_unix, change_stream_type_fixture)
{
// UNIX connect params
auto unix_params = default_connect_params();
unix_params.server_address.emplace_unix_path(default_unix_path);
test_case_t test_cases[] = {
{"sync_unix_tcpssl", sync_fns, unix_params, tcp_ssl_params},
{"async_unix_tcpssl", async_fns, unix_params, tcp_ssl_params},
{"async_tcpssl_unix", async_fns, tcp_ssl_params, unix_params },
{"async_tcp_unix", async_fns, tcp_params, unix_params },
};
run(test_cases);
}
#endif
BOOST_AUTO_TEST_SUITE_END() // test_reconnect
} // namespace

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,8 @@
#include <boost/mysql/detail/config.hpp>
#ifdef BOOST_MYSQL_CXX14
#include <boost/mysql/column_type.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/pfr.hpp>
@ -28,17 +24,22 @@
#include <boost/pfr/ops.hpp>
#include <boost/test/unit_test.hpp>
#include <array>
#include <cstdint>
#include <tuple>
#include "test_common/check_meta.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/connect_params_builder.hpp"
#include "test_integration/metadata_validator.hpp"
#include "test_integration/static_rows.hpp"
#include "test_integration/tcp_network_fixture.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::span;
using boost::test_tools::per_element;
// Note: the dynamic interface is already covered by stored_procedures, multi_queries, prepared_statements and
// spotchecks
@ -75,11 +76,14 @@ void validate_multifield_meta(metadata_collection_view meta)
);
}
void validate_multified_rows(boost::span<const row_multifield> r)
std::array<row_multifield, 2> expected_multifield_rows()
{
BOOST_TEST_REQUIRE(r.size() == 2u);
BOOST_TEST((r[0] == row_multifield{1.1f, 11, "aaa"}));
BOOST_TEST((r[1] == row_multifield{{}, 22, "bbb"}));
return {
{
{1.1f, 11, "aaa"},
{{}, 22, "bbb"},
}
};
}
constexpr const char* multifield_bad_msg =
@ -90,29 +94,31 @@ constexpr const char* multifield_bad_msg =
BOOST_AUTO_TEST_SUITE(singlefn)
BOOST_FIXTURE_TEST_CASE(describe_structs, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(describe_structs, any_connection_fixture)
{
connect();
static_results<row_multifield> result;
conn.execute("SELECT * FROM multifield_table ORDER BY id", result);
conn.async_execute("SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_no_error();
// Verify results
validate_multifield_meta(result.meta());
validate_multified_rows(result.rows());
BOOST_TEST(result.rows() == expected_multifield_rows(), per_element());
BOOST_TEST(result.affected_rows() == 0u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u);
BOOST_TEST(result.info() == "");
}
BOOST_FIXTURE_TEST_CASE(tuples, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(tuples, any_connection_fixture)
{
connect();
using tuple_t = std::tuple<int, std::string, int, boost::optional<float>>; // trailing fields discarded
static_results<tuple_t> result;
conn.execute("SELECT * FROM multifield_table ORDER BY id", result);
conn.async_execute("SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_no_error();
// Verify results
validate_multifield_meta(result.meta());
@ -126,12 +132,13 @@ BOOST_FIXTURE_TEST_CASE(tuples, tcp_network_fixture)
}
#if BOOST_PFR_CORE_NAME_ENABLED
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, any_connection_fixture)
{
connect();
static_results<pfr_by_name<row_multifield_pfr>> result;
conn.execute("SELECT * FROM multifield_table ORDER BY id", result);
conn.async_execute("SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_no_error();
// Verify results
validate_multifield_meta(result.meta());
@ -146,12 +153,17 @@ BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, tcp_network_fixture)
#endif
#if BOOST_PFR_ENABLED && defined(BOOST_MYSQL_CXX14)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, any_connection_fixture)
{
connect();
static_results<pfr_by_position<row_multifield_pfr_literal>> result;
conn.execute("SELECT id, field_int, field_double FROM multifield_table ORDER BY id", result);
conn.async_execute(
"SELECT id, field_int, field_double FROM multifield_table ORDER BY id",
result,
as_netresult
)
.validate_no_error();
// Verify results
check_meta(result.meta(), {column_type::int_, column_type::int_, column_type::double_});
@ -166,26 +178,22 @@ BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, tcp_network_fixture)
#endif
// This spotchecks having a repeated empty row type, too
BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(multi_resultset, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
connect(connect_params_builder().multi_queries(true).build());
start_transaction();
static_results<row_multifield, empty, row_2fields, empty> result;
conn.execute(
R"%(
SELECT * FROM multifield_table;
DELETE FROM updates_table;
SELECT * FROM one_row_table;
SET @v1 = 2
)%",
result
);
constexpr const char* query =
"SELECT * FROM multifield_table;"
"DELETE FROM updates_table;"
"SELECT * FROM one_row_table;"
"SET @v1 = 2";
conn.async_execute(query, result, as_netresult).validate_no_error();
// Validate results
validate_multifield_meta(result.meta<0>());
validate_multified_rows(result.rows<0>());
BOOST_TEST(result.rows<0>() == expected_multifield_rows(), per_element());
BOOST_TEST(result.affected_rows<0>() == 0u);
BOOST_TEST(result.warning_count<0>() == 0u);
BOOST_TEST(result.last_insert_id<0>() == 0u);
@ -198,9 +206,11 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.last_insert_id<1>() == 0u);
BOOST_TEST(result.info<1>() == "");
const row_2fields expected_2fields[]{
{1, std::string("f0")}
};
validate_2fields_meta(result.meta<2>(), "one_row_table");
BOOST_TEST_REQUIRE(result.rows<2>().size() == 1u);
BOOST_TEST_REQUIRE((result.rows<2>()[0] == row_2fields{1, std::string("f0")}));
BOOST_TEST(result.rows<2>() == expected_2fields, per_element());
BOOST_TEST(result.affected_rows<2>() == 0u);
BOOST_TEST(result.warning_count<2>() == 0u);
BOOST_TEST(result.last_insert_id<2>() == 0u);
@ -214,93 +224,74 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.info<3>() == "");
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_results<row_multifield_bad> result;
conn.execute("SELECT * FROM multifield_table ORDER BY id", result, ec, diag);
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == multifield_bad_msg);
conn.async_execute("SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_error(client_errc::metadata_check_failed, multifield_bad_msg);
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_empty_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_empty_resultset, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_results<std::tuple<int>> result;
conn.execute("SET @v1 = 2", result, ec, diag);
const char* expected_msg =
"Field in position 0 can't be mapped: there are more fields in your C++ data type than in your query";
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == expected_msg);
static_results<std::tuple<int>> result;
conn.async_execute("SET @v1 = 2", result, as_netresult)
.validate_error(client_errc::metadata_check_failed, expected_msg);
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_subsequent_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_subsequent_resultset, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
connect(connect_params_builder().multi_queries(true).build());
error_code ec;
diagnostics diag;
static_results<empty, row_multifield_bad> result;
conn.execute("SET @v1 = 2; SELECT * FROM multifield_table ORDER BY id", result, ec, diag);
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == multifield_bad_msg);
conn.async_execute("SET @v1 = 2; SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_error(client_errc::metadata_check_failed, multifield_bad_msg);
}
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_results<row_2fields, empty> result;
conn.execute("SELECT * FROM one_row_table", result, ec, diag);
BOOST_TEST(ec == client_errc::num_resultsets_mismatch);
conn.async_execute("SELECT * FROM one_row_table", result, as_netresult)
.validate_error(client_errc::num_resultsets_mismatch);
}
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch_empty_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch_empty_resultset, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_results<empty, empty> result;
conn.execute("SET @v1 = 2", result, ec, diag);
BOOST_TEST(ec == client_errc::num_resultsets_mismatch);
conn.async_execute("SET @v1 = 2", result, as_netresult)
.validate_error(client_errc::num_resultsets_mismatch);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(multifn)
BOOST_FIXTURE_TEST_CASE(describe_structs, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(describe_structs, any_connection_fixture)
{
connect();
// Start
static_execution_state<row_multifield> result;
conn.start_execution("SELECT * FROM multifield_table WHERE id = 1", result);
conn.async_start_execution("SELECT * FROM multifield_table WHERE id = 1", result, as_netresult)
.validate_no_error();
validate_multifield_meta(result.meta());
BOOST_TEST(result.should_read_rows());
// Read rows
std::array<row_multifield, 3> rws;
std::size_t num_rows = conn.read_some_rows(result, boost::span<row_multifield>(rws));
std::size_t num_rows = conn.async_read_some_rows(result, span<row_multifield>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST((rws[0] == row_multifield{1.1f, 11, "aaa"}));
// Read again, in case the EOF came separately
num_rows = conn.read_some_rows(result, boost::span<row_multifield>(rws));
num_rows = conn.async_read_some_rows(result, span<row_multifield>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.complete());
BOOST_TEST(result.affected_rows() == 0u);
@ -309,7 +300,7 @@ BOOST_FIXTURE_TEST_CASE(describe_structs, tcp_network_fixture)
BOOST_TEST(result.info() == "");
}
BOOST_FIXTURE_TEST_CASE(tuples, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(tuples, any_connection_fixture)
{
connect();
@ -317,18 +308,19 @@ BOOST_FIXTURE_TEST_CASE(tuples, tcp_network_fixture)
// Start
static_execution_state<tuple_t> result;
conn.start_execution("SELECT * FROM multifield_table WHERE id = 1", result);
conn.async_start_execution("SELECT * FROM multifield_table WHERE id = 1", result, as_netresult)
.validate_no_error();
validate_multifield_meta(result.meta());
BOOST_TEST(result.should_read_rows());
// Read rows
std::array<tuple_t, 3> rws;
std::size_t num_rows = conn.read_some_rows(result, boost::span<tuple_t>(rws));
std::size_t num_rows = conn.async_read_some_rows(result, span<tuple_t>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST((rws[0] == tuple_t{1, "aaa", 11}));
// Read again, in case the EOF came separately
num_rows = conn.read_some_rows(result, boost::span<tuple_t>(rws));
num_rows = conn.async_read_some_rows(result, span<tuple_t>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.complete());
BOOST_TEST(result.affected_rows() == 0u);
@ -338,24 +330,26 @@ BOOST_FIXTURE_TEST_CASE(tuples, tcp_network_fixture)
}
#if BOOST_PFR_CORE_NAME_ENABLED
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, any_connection_fixture)
{
connect();
// Start
static_execution_state<pfr_by_name<row_multifield_pfr>> result;
conn.start_execution("SELECT * FROM multifield_table WHERE id = 1", result);
conn.async_start_execution("SELECT * FROM multifield_table WHERE id = 1", result, as_netresult)
.validate_no_error();
validate_multifield_meta(result.meta());
BOOST_TEST(result.should_read_rows());
// Read rows
std::array<row_multifield_pfr, 3> rws;
std::size_t num_rows = conn.read_some_rows(result, boost::span<row_multifield_pfr>(rws));
std::size_t num_rows = conn.async_read_some_rows(result, span<row_multifield_pfr>(rws), as_netresult)
.get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST(boost::pfr::eq(rws[0], row_multifield_pfr{1.1f, 11, "aaa"}));
// Read again, in case the EOF came separately
num_rows = conn.read_some_rows(result, boost::span<row_multifield_pfr>(rws));
num_rows = conn.async_read_some_rows(result, span<row_multifield_pfr>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.complete());
BOOST_TEST(result.affected_rows() == 0u);
@ -366,24 +360,30 @@ BOOST_FIXTURE_TEST_CASE(pfr_structs_by_name, tcp_network_fixture)
#endif
#if BOOST_PFR_ENABLED && defined(BOOST_MYSQL_CXX14)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, any_connection_fixture)
{
connect();
// Start
static_execution_state<pfr_by_position<row_multifield_pfr_literal>> result;
conn.start_execution("SELECT id, field_int, field_double FROM multifield_table WHERE id = 1", result);
conn.async_start_execution(
"SELECT id, field_int, field_double FROM multifield_table WHERE id = 1",
result,
as_netresult
)
.validate_no_error();
check_meta(result.meta(), {column_type::int_, column_type::int_, column_type::double_});
BOOST_TEST(result.should_read_rows());
// Read rows
std::array<row_multifield_pfr_literal, 3> rws;
std::size_t num_rows = conn.read_some_rows(result, boost::span<row_multifield_pfr_literal>(rws));
auto num_rows = conn.async_read_some_rows(result, span<row_multifield_pfr_literal>(rws), as_netresult)
.get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST(boost::pfr::eq(rws[0], row_multifield_pfr_literal{1, 11, 0.1}));
// Read again, in case the EOF came separately
num_rows = conn.read_some_rows(result, boost::span<row_multifield_pfr_literal>(rws));
num_rows = conn.async_read_some_rows(result, span<row_multifield_pfr_literal>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.complete());
BOOST_TEST(result.affected_rows() == 0u);
@ -394,34 +394,30 @@ BOOST_FIXTURE_TEST_CASE(pfr_structs_by_position, tcp_network_fixture)
#endif
// This spotchecks having repeated empty row types, too
BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(multi_resultset, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
connect(connect_params_builder().multi_queries(true).build());
start_transaction();
// Start
constexpr const char* query =
"SELECT * FROM multifield_table WHERE id = 1;"
"DELETE FROM updates_table;"
"SELECT * FROM one_row_table;"
"SET @v1 = 2";
static_execution_state<row_multifield, empty, row_2fields, empty> result;
conn.start_execution(
R"%(
SELECT * FROM multifield_table WHERE id = 1;
DELETE FROM updates_table;
SELECT * FROM one_row_table;
SET @v1 = 2
)%",
result
);
conn.async_start_execution(query, result, as_netresult).validate_no_error();
validate_multifield_meta(result.meta());
BOOST_TEST(result.should_read_rows());
// Read rows (r1)
std::array<row_multifield, 3> rws;
std::size_t num_rows = conn.read_some_rows(result, boost::span<row_multifield>(rws));
std::size_t num_rows = conn.async_read_some_rows(result, span<row_multifield>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST((rws[0] == row_multifield{1.1f, 11, "aaa"}));
// Read again, in case the EOF came separately (r1)
num_rows = conn.read_some_rows(result, boost::span<row_multifield>(rws));
num_rows = conn.async_read_some_rows(result, span<row_multifield>(rws), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.should_read_head());
BOOST_TEST(result.affected_rows() == 0u);
@ -430,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.info() == "");
// Next resultset (r2, empty)
conn.read_resultset_head(result);
conn.async_read_resultset_head(result, as_netresult).validate_no_error();
BOOST_TEST(result.should_read_head());
BOOST_TEST(result.meta().size() == 0u);
BOOST_TEST(result.affected_rows() == 3u);
@ -439,18 +435,18 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.info() == "");
// Next resultset (r3)
conn.read_resultset_head(result);
conn.async_read_resultset_head(result, as_netresult).validate_no_error();
BOOST_TEST(result.should_read_rows());
validate_2fields_meta(result.meta(), "one_row_table");
// Read rows (r3)
std::array<row_2fields, 3> rws2;
num_rows = conn.read_some_rows(result, boost::span<row_2fields>(rws2));
num_rows = conn.async_read_some_rows(result, span<row_2fields>(rws2), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 1u);
BOOST_TEST((rws2[0] == row_2fields{1, std::string("f0")}));
// Read again, in case the EOF came separately (r3)
num_rows = conn.read_some_rows(result, boost::span<row_2fields>(rws2));
num_rows = conn.async_read_some_rows(result, span<row_2fields>(rws2), as_netresult).get();
BOOST_TEST_REQUIRE(num_rows == 0u);
BOOST_TEST(result.should_read_head());
BOOST_TEST(result.affected_rows() == 0u);
@ -459,7 +455,7 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.info() == "");
// Next resultset (r4, empty)
conn.read_resultset_head(result);
conn.async_read_resultset_head(result, as_netresult).validate_no_error();
BOOST_TEST(result.complete());
BOOST_TEST(result.meta().size() == 0u);
BOOST_TEST(result.affected_rows() == 0u);
@ -468,83 +464,64 @@ BOOST_FIXTURE_TEST_CASE(multi_resultset, tcp_network_fixture)
BOOST_TEST(result.info() == "");
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_execution_state<row_multifield_bad> result;
conn.start_execution("SELECT * FROM multifield_table ORDER BY id", result, ec, diag);
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == multifield_bad_msg);
conn.async_start_execution("SELECT * FROM multifield_table ORDER BY id", result, as_netresult)
.validate_error(client_errc::metadata_check_failed, multifield_bad_msg);
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_empty_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_empty_resultset, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_execution_state<std::tuple<int>> result;
conn.start_execution("SET @v1 = 2", result, ec, diag);
const char* expected_msg =
"Field in position 0 can't be mapped: there are more fields in your C++ data type than in your query";
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == expected_msg);
static_execution_state<std::tuple<int>> result;
conn.async_start_execution("SET @v1 = 2", result, as_netresult)
.validate_error(client_errc::metadata_check_failed, expected_msg);
}
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
static_execution_state<row_2fields, empty> result;
// Start execution
conn.start_execution("SELECT * FROM empty_table", result);
conn.async_start_execution("SELECT * FROM empty_table", result, as_netresult).validate_no_error();
// Error is detected when reading the OK packet in read_some_rows
std::array<row_2fields, 3> storage;
conn.read_some_rows(result, boost::span<row_2fields>(storage), ec, diag);
BOOST_TEST(ec == client_errc::num_resultsets_mismatch);
conn.async_read_some_rows(result, span<row_2fields>(storage), as_netresult)
.validate_error(client_errc::num_resultsets_mismatch);
}
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch_empty_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(num_resultsets_mismatch_empty_resultset, any_connection_fixture)
{
connect();
error_code ec;
diagnostics diag;
// Start
static_execution_state<empty, empty> result;
conn.start_execution("SET @v1 = 2", result, ec, diag);
BOOST_TEST(ec == client_errc::num_resultsets_mismatch);
conn.async_start_execution("SET @v1 = 2", result, as_netresult)
.validate_error(client_errc::num_resultsets_mismatch);
}
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_subsequent_resultset, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(metadata_check_failed_subsequent_resultset, any_connection_fixture)
{
params.set_multi_queries(true);
connect();
connect(connect_params_builder().multi_queries(true).build());
error_code ec;
diagnostics diag;
static_execution_state<empty, row_multifield_bad> result;
// Start execution goes OK
conn.start_execution("SET @v1 = 2; SELECT * FROM multifield_table", result);
conn.async_start_execution("SET @v1 = 2; SELECT * FROM multifield_table", result, as_netresult)
.validate_no_error();
// Error is detected when reading next head
conn.read_resultset_head(result, ec, diag);
BOOST_TEST(ec == client_errc::metadata_check_failed);
BOOST_TEST(diag.client_message() == multifield_bad_msg);
conn.async_read_resultset_head(result, as_netresult)
.validate_error(client_errc::metadata_check_failed, multifield_bad_msg);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -7,41 +7,42 @@
#include <boost/mysql/column_type.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/results.hpp>
#include <boost/test/unit_test.hpp>
#include "test_common/check_meta.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/tcp_network_fixture.hpp"
#include "test_integration/any_connection_fixture.hpp"
#include "test_integration/metadata_validator.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::test_tools::per_element;
namespace {
BOOST_AUTO_TEST_SUITE(test_stored_procedures)
BOOST_FIXTURE_TEST_CASE(without_selects, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(without_selects, any_connection_fixture)
{
connect();
start_transaction();
// Statement
auto stmt = conn.prepare_statement("CALL sp_insert(?)");
auto stmt = conn.async_prepare_statement("CALL sp_insert(?)", as_netresult).get();
// Call the procedure
results result;
conn.execute(stmt.bind("abc"), result);
conn.async_execute(stmt.bind("abc"), result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 1u);
BOOST_TEST(result.meta().size() == 0u);
BOOST_TEST(result.rows().empty());
BOOST_TEST(result.rows() == rows(), per_element());
BOOST_TEST(result.affected_rows() == 1u);
BOOST_TEST(result.warning_count() == 0u);
BOOST_TEST(result.last_insert_id() == 0u); // this refers to the CALL, not to the INSERT
@ -49,31 +50,31 @@ BOOST_FIXTURE_TEST_CASE(without_selects, tcp_network_fixture)
BOOST_TEST(result.out_params() == row_view());
// Verify it took place
conn.execute("SELECT field_varchar FROM inserts_table", result);
BOOST_TEST(result.rows().at(0).at(0).as_string() == "abc");
conn.async_execute("SELECT field_varchar FROM inserts_table", result, as_netresult).validate_no_error();
BOOST_TEST(result.rows() == makerows(1, "abc"), per_element());
}
BOOST_FIXTURE_TEST_CASE(with_one_select, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(with_one_select, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_select_1(?)");
auto stmt = conn.async_prepare_statement("CALL sp_select_1(?)", as_netresult).get();
// Call the procedure
results result;
conn.execute(stmt.bind("abc"), result);
conn.async_execute(stmt.bind("abc"), result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 2u);
validate_2fields_meta(result[0].meta(), "one_row_table");
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"));
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(result[0].affected_rows() == 0u);
BOOST_TEST(result[0].warning_count() == 0u);
BOOST_TEST(result[0].last_insert_id() == 0u);
BOOST_TEST(result[0].info() == "");
BOOST_TEST(result[1].meta().size() == 0u);
BOOST_TEST(result[1].rows().size() == 0u);
BOOST_TEST(result[1].rows() == rows(), per_element());
BOOST_TEST(result[1].affected_rows() == 0u);
BOOST_TEST(result[1].warning_count() == 0u);
BOOST_TEST(result[1].last_insert_id() == 0u);
@ -81,33 +82,33 @@ BOOST_FIXTURE_TEST_CASE(with_one_select, tcp_network_fixture)
BOOST_TEST(result.out_params() == row_view());
}
BOOST_FIXTURE_TEST_CASE(with_two_selects, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(with_two_selects, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_select_2(?, ?)");
auto stmt = conn.async_prepare_statement("CALL sp_select_2(?, ?)", as_netresult).get();
// Call the procedure
results result;
conn.execute(stmt.bind("abc", 42), result);
conn.async_execute(stmt.bind("abc", 42), result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 3u);
validate_2fields_meta(result[0].meta(), "one_row_table");
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"));
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(result[0].affected_rows() == 0u);
BOOST_TEST(result[0].warning_count() == 0u);
BOOST_TEST(result[0].last_insert_id() == 0u);
BOOST_TEST(result[0].info() == "");
check_meta(result[1].meta(), {column_type::varchar, column_type::int_});
BOOST_TEST(result[1].rows() == makerows(2, "abc", 42));
BOOST_TEST(result[1].rows() == makerows(2, "abc", 42), per_element());
BOOST_TEST(result[1].affected_rows() == 0u);
BOOST_TEST(result[1].warning_count() == 0u);
BOOST_TEST(result[1].last_insert_id() == 0u);
BOOST_TEST(result[1].info() == "");
BOOST_TEST(result[2].meta().size() == 0u);
BOOST_TEST(result[2].rows().size() == 0u);
BOOST_TEST(result[2].rows() == rows(), per_element());
BOOST_TEST(result[2].affected_rows() == 0u);
BOOST_TEST(result[2].warning_count() == 0u);
BOOST_TEST(result[2].last_insert_id() == 0u);
@ -115,23 +116,23 @@ BOOST_FIXTURE_TEST_CASE(with_two_selects, tcp_network_fixture)
BOOST_TEST(result.out_params() == row_view());
}
BOOST_FIXTURE_TEST_CASE(with_two_selects_multifn, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(with_two_selects_multifn, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_select_2(?, ?)");
auto stmt = conn.async_prepare_statement("CALL sp_select_2(?, ?)", as_netresult).get();
// Call the procedure
execution_state st;
conn.start_execution(stmt.bind("abc", 42), st);
conn.async_start_execution(stmt.bind("abc", 42), st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
validate_2fields_meta(st.meta(), "one_row_table");
// Read rows for the 1st select
auto rv = conn.read_some_rows(st);
auto rv = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST_REQUIRE(st.should_read_head());
BOOST_TEST(rv == makerows(2, 1, "f0"));
BOOST_TEST(rv == makerows(2, 1, "f0"), per_element());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
@ -139,14 +140,14 @@ BOOST_FIXTURE_TEST_CASE(with_two_selects_multifn, tcp_network_fixture)
BOOST_TEST(!st.is_out_params());
// Read 2nd resultset's head
conn.read_resultset_head(st);
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
check_meta(st.meta(), {column_type::varchar, column_type::int_});
// Read 2nd resultset's rows
rv = conn.read_some_rows(st);
rv = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST_REQUIRE(st.should_read_head());
BOOST_TEST(rv == makerows(2, "abc", 42));
BOOST_TEST(rv == makerows(2, "abc", 42), per_element());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
@ -154,7 +155,7 @@ BOOST_FIXTURE_TEST_CASE(with_two_selects_multifn, tcp_network_fixture)
BOOST_TEST(!st.is_out_params());
// Read final resultset
conn.read_resultset_head(st);
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.complete());
BOOST_TEST(st.meta().size() == 0u);
BOOST_TEST(st.affected_rows() == 0u);
@ -164,23 +165,23 @@ BOOST_FIXTURE_TEST_CASE(with_two_selects_multifn, tcp_network_fixture)
BOOST_TEST(!st.is_out_params());
}
BOOST_FIXTURE_TEST_CASE(output_params_not_bound, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(output_params_not_bound, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_outparams(?, @var1, @var2)");
auto stmt = conn.async_prepare_statement("CALL sp_outparams(?, @var1, @var2)", as_netresult).get();
// Call the procedure
results result;
conn.execute(stmt.bind(10), result);
conn.async_execute(stmt.bind(10), result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 2u);
validate_2fields_meta(result[0].meta(), "one_row_table");
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"));
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(result[1].meta().size() == 0u);
BOOST_TEST(result[1].rows().size() == 0u);
BOOST_TEST(result[1].rows() == rows(), per_element());
BOOST_TEST(result[1].affected_rows() == 0u);
BOOST_TEST(result[1].warning_count() == 0u);
BOOST_TEST(result[1].last_insert_id() == 0u);
@ -188,31 +189,31 @@ BOOST_FIXTURE_TEST_CASE(output_params_not_bound, tcp_network_fixture)
BOOST_TEST(result.out_params() == row_view());
}
BOOST_FIXTURE_TEST_CASE(output_params_bound, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(output_params_bound, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_outparams(?, ?, ?)");
auto stmt = conn.async_prepare_statement("CALL sp_outparams(?, ?, ?)", as_netresult).get();
// Call the procedure
results result;
conn.execute(stmt.bind(10, nullptr, 30), result);
conn.async_execute(stmt.bind(10, nullptr, 30), result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 3u);
validate_2fields_meta(result[0].meta(), "one_row_table");
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"));
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(!result[0].is_out_params());
check_meta(result[1].meta(), {column_type::int_, column_type::int_});
BOOST_TEST(result[1].rows() == makerows(2, 10, 31));
BOOST_TEST(result[1].rows() == makerows(2, 10, 31), per_element());
BOOST_TEST(result[1].affected_rows() == 0u);
BOOST_TEST(result[1].warning_count() == 0u);
BOOST_TEST(result[1].last_insert_id() == 0u);
BOOST_TEST(result[1].info() == "");
BOOST_TEST(result[1].is_out_params());
BOOST_TEST(result[2].meta().size() == 0u);
BOOST_TEST(result[2].rows().size() == 0u);
BOOST_TEST(result[2].rows() == rows(), per_element());
BOOST_TEST(result[2].affected_rows() == 0u);
BOOST_TEST(result[2].warning_count() == 0u);
BOOST_TEST(result[2].last_insert_id() == 0u);
@ -221,34 +222,34 @@ BOOST_FIXTURE_TEST_CASE(output_params_bound, tcp_network_fixture)
BOOST_TEST(result.out_params() == makerow(10, 31));
}
BOOST_FIXTURE_TEST_CASE(output_params_bound_multifn, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(output_params_bound_multifn, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_outparams(?, ?, ?)");
auto stmt = conn.async_prepare_statement("CALL sp_outparams(?, ?, ?)", as_netresult).get();
// Call the procedure
execution_state st;
conn.start_execution(stmt.bind(10, nullptr, 30), st);
conn.async_start_execution(stmt.bind(10, nullptr, 30), st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
validate_2fields_meta(st.meta(), "one_row_table");
// 1st resultset, rows
auto rv = conn.read_some_rows(st);
BOOST_TEST(rv == makerows(2, 1, "f0"));
auto rv = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST(rv == makerows(2, 1, "f0"), per_element());
BOOST_TEST_REQUIRE(st.should_read_head());
BOOST_TEST(!st.is_out_params());
// out params, head
conn.read_resultset_head(st);
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.should_read_rows());
check_meta(st.meta(), {column_type::int_, column_type::int_});
// out params, rows and eof
rv = conn.read_some_rows(st);
rv = conn.async_read_some_rows(st, as_netresult).get();
BOOST_TEST_REQUIRE(st.should_read_head());
BOOST_TEST(rv == makerows(2, 10, 31));
BOOST_TEST(rv == makerows(2, 10, 31), per_element());
BOOST_TEST(st.affected_rows() == 0u);
BOOST_TEST(st.warning_count() == 0u);
BOOST_TEST(st.last_insert_id() == 0u);
@ -256,7 +257,7 @@ BOOST_FIXTURE_TEST_CASE(output_params_bound_multifn, tcp_network_fixture)
BOOST_TEST(st.is_out_params());
// final eof
conn.read_resultset_head(st);
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
BOOST_TEST_REQUIRE(st.complete());
BOOST_TEST(st.meta().size() == 0u);
BOOST_TEST(st.affected_rows() == 0u);
@ -266,38 +267,33 @@ BOOST_FIXTURE_TEST_CASE(output_params_bound_multifn, tcp_network_fixture)
BOOST_TEST(!st.is_out_params());
}
BOOST_FIXTURE_TEST_CASE(with_signal, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(with_signal, any_connection_fixture)
{
connect();
// Statement
auto stmt = conn.prepare_statement("CALL sp_signal()");
auto stmt = conn.async_prepare_statement("CALL sp_signal()", as_netresult).get();
// Call the procedure. It should fail, since we're invoking SIGNAL
results result;
error_code err;
diagnostics diag;
conn.execute(stmt.bind(), result, err, diag);
// Verify results
BOOST_TEST(err == common_server_errc::er_no);
BOOST_TEST(diag.server_message() == "An error occurred");
conn.async_execute(stmt.bind(), result, as_netresult)
.validate_error(common_server_errc::er_no, "An error occurred");
}
BOOST_FIXTURE_TEST_CASE(with_query, tcp_network_fixture)
BOOST_FIXTURE_TEST_CASE(with_query, any_connection_fixture)
{
connect();
// Call the procedure
results result;
conn.execute("CALL sp_outparams(42, @var1, @var2)", result);
conn.async_execute("CALL sp_outparams(42, @var1, @var2)", result, as_netresult).validate_no_error();
// Verify results
BOOST_TEST_REQUIRE(result.size() == 2u);
validate_2fields_meta(result[0].meta(), "one_row_table");
BOOST_TEST(result[0].rows() == makerows(2, 1, "f0"));
BOOST_TEST(result[1].meta().size() == 0u);
BOOST_TEST(result[1].rows().size() == 0u);
BOOST_TEST(result[1].rows() == rows(), per_element());
BOOST_TEST(result[1].affected_rows() == 0u);
BOOST_TEST(result[1].warning_count() == 0u);
BOOST_TEST(result[1].last_insert_id() == 0u);

View File

@ -1,37 +0,0 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_RUN_COROUTINE_HPP
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_RUN_COROUTINE_HPP
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_future.hpp>
#include "test_common/netfun_helpers.hpp"
namespace boost {
namespace mysql {
namespace test {
inline void run_coroutine(asio::any_io_executor ex, std::function<asio::awaitable<void>(void)> coro)
{
auto fut = boost::asio::co_spawn(ex, std::move(coro), boost::asio::use_future);
run_until_completion(ex);
fut.get();
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif // BOOST_ASIO_HAS_CO_AWAIT
#endif

View File

@ -11,7 +11,6 @@
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <algorithm>
@ -23,13 +22,12 @@
#include <vector>
#include "test_common/buffer_concat.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_unit/test_stream.hpp"
using namespace boost::mysql::test;
using boost::mysql::error_code;
static boost::asio::io_context ctx;
std::size_t boost::mysql::test::test_stream::get_size_to_read(std::size_t buffer_size) const
{
auto it = read_break_offsets_.upper_bound(num_bytes_read_);
@ -142,7 +140,7 @@ struct boost::mysql::test::test_stream::write_op : boost::asio::coroutine
boost::mysql::test::test_stream::executor_type boost::mysql::test::test_stream::get_executor()
{
return ctx.get_executor();
return global_context_executor();
}
// Reading

View File

@ -6,18 +6,20 @@
//
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/any_resumable_ref.hpp>
#include <boost/mysql/detail/engine_impl.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/test/unit_test.hpp>
@ -26,7 +28,10 @@
#include <cstdint>
#include <cstring>
#include "test_common/create_diagnostics.hpp"
#include "test_common/netfun_maker.hpp"
#include "test_common/network_result.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql::test;
@ -169,21 +174,49 @@ public:
}
};
using test_engine = engine_impl<mock_engine_stream>;
// Helpers to run the sync and async versions uniformly.
// engine_impl uses any_completion_handler, so it needs a wrapper.
template <class CompletionToken>
void do_async_run(test_engine& eng, any_resumable_ref resumable, CompletionToken&& token)
// engine_impl must be adapted because async_run() takes a completion handler,
// rather than a generic completion token
struct test_engine
{
eng.async_run(resumable, std::forward<CompletionToken>(token));
}
engine_impl<mock_engine_stream> value;
const auto sync_fn = netfun_maker_mem<void, test_engine, any_resumable_ref>::sync_errc_noerrinfo(
&test_engine::run
);
const auto async_fn = netfun_maker_fn<void, test_engine&, any_resumable_ref>::async_noerrinfo(&do_async_run);
using signature_t = decltype(sync_fn);
struct initiation_t
{
void operator()(
asio::any_completion_handler<void(error_code)> handler,
engine* eng,
any_resumable_ref resumable
) const
{
eng->async_run(resumable, std::move(handler));
}
};
void run(any_resumable_ref resumable, error_code& err) { value.run(resumable, err); }
template <class CompletionToken>
auto async_run(any_resumable_ref resumable, CompletionToken&& token)
-> decltype(asio::async_initiate<CompletionToken, void(error_code)>(
initiation_t{},
token,
&value,
resumable
))
{
return asio::async_initiate<CompletionToken, void(error_code)>(
initiation_t{},
token,
&value,
resumable
);
}
};
using netmaker_t = netfun_maker<void, test_engine, any_resumable_ref>;
const auto sync_fn = netmaker_t::sync_errc_nodiag(&test_engine::run);
const auto async_fn = netmaker_t::async_nodiag(&test_engine::async_run);
using signature_t = netmaker_t::signature;
// A mock for a sans-io algorithm. Can be converted to any_resumable_ref
struct mock_algo
@ -229,15 +262,14 @@ BOOST_AUTO_TEST_CASE(next_action_read)
// Setup
std::array<std::uint8_t, 8> buff{};
mock_algo algo(next_action::read({buff, tc.ssl_active}));
asio::io_context ctx;
test_engine eng(ctx.get_executor());
test_engine eng{global_context_executor()};
tc.fn(eng, any_resumable_ref(algo)).validate_no_error();
BOOST_TEST(eng.stream().calls.size() == 1u);
BOOST_TEST(eng.stream().calls[0].type() == next_action_type::read);
BOOST_TEST(eng.stream().calls[0].read_args().use_ssl == tc.ssl_active);
BOOST_TEST(eng.stream().calls[0].read_args().buffer.data() == buff.data());
BOOST_TEST(eng.stream().calls[0].read_args().buffer.size() == buff.size());
tc.fn(eng, any_resumable_ref(algo)).validate_no_error_nodiag();
BOOST_TEST(eng.value.stream().calls.size() == 1u);
BOOST_TEST(eng.value.stream().calls[0].type() == next_action_type::read);
BOOST_TEST(eng.value.stream().calls[0].read_args().use_ssl == tc.ssl_active);
BOOST_TEST(eng.value.stream().calls[0].read_args().buffer.data() == buff.data());
BOOST_TEST(eng.value.stream().calls[0].read_args().buffer.size() == buff.size());
algo.check_calls({
{error_code(), 0u},
{error_code(), 8u}
@ -269,15 +301,14 @@ BOOST_AUTO_TEST_CASE(next_action_write)
// Setup
const std::array<std::uint8_t, 4> buff{};
mock_algo algo(next_action::write({buff, tc.ssl_active}));
asio::io_context ctx;
test_engine eng(ctx.get_executor());
test_engine eng{global_context_executor()};
tc.fn(eng, any_resumable_ref(algo)).validate_no_error();
BOOST_TEST(eng.stream().calls.size() == 1u);
BOOST_TEST(eng.stream().calls[0].type() == next_action_type::write);
BOOST_TEST(eng.stream().calls[0].write_args().use_ssl == tc.ssl_active);
BOOST_TEST(eng.stream().calls[0].write_args().buffer.data() == buff.data());
BOOST_TEST(eng.stream().calls[0].write_args().buffer.size() == buff.size());
tc.fn(eng, any_resumable_ref(algo)).validate_no_error_nodiag();
BOOST_TEST(eng.value.stream().calls.size() == 1u);
BOOST_TEST(eng.value.stream().calls[0].type() == next_action_type::write);
BOOST_TEST(eng.value.stream().calls[0].write_args().use_ssl == tc.ssl_active);
BOOST_TEST(eng.value.stream().calls[0].write_args().buffer.data() == buff.data());
BOOST_TEST(eng.value.stream().calls[0].write_args().buffer.size() == buff.size());
algo.check_calls({
{error_code(), 0u},
{error_code(), 4u}
@ -312,12 +343,11 @@ BOOST_AUTO_TEST_CASE(next_action_other)
{
// Setup
mock_algo algo(tc.act);
asio::io_context ctx;
test_engine eng(ctx.get_executor());
test_engine eng{global_context_executor()};
tc.fn(eng, any_resumable_ref(algo)).validate_no_error();
BOOST_TEST(eng.stream().calls.size() == 1u);
BOOST_TEST(eng.stream().calls[0].type() == tc.act.type());
tc.fn(eng, any_resumable_ref(algo)).validate_no_error_nodiag();
BOOST_TEST(eng.value.stream().calls.size() == 1u);
BOOST_TEST(eng.value.stream().calls[0].type() == tc.act.type());
algo.check_calls({
{error_code(), 0u},
{error_code(), 0u}
@ -359,12 +389,14 @@ BOOST_AUTO_TEST_CASE(stream_errors)
{
// Setup
mock_algo algo(tc.act);
asio::io_context ctx;
test_engine eng(ctx.get_executor(), asio::error::already_open);
test_engine eng{
{global_context_executor(), asio::error::already_open}
};
tc.fn(eng, any_resumable_ref(algo)).validate_no_error(); // Error gets swallowed by the algo
BOOST_TEST(eng.stream().calls.size() == 1u);
BOOST_TEST(eng.stream().calls[0].type() == tc.act.type());
tc.fn(eng, any_resumable_ref(algo))
.validate_no_error_nodiag(); // Error gets swallowed by the algo
BOOST_TEST(eng.value.stream().calls.size() == 1u);
BOOST_TEST(eng.value.stream().calls[0].type() == tc.act.type());
algo.check_calls({
{error_code(), 0u},
{asio::error::already_open, 0u}
@ -395,11 +427,11 @@ BOOST_AUTO_TEST_CASE(resume_error_immediate)
{
// Setup
mock_algo algo(next_action(tc.ec));
asio::io_context ctx;
test_engine eng(ctx.get_executor());
test_engine eng{global_context_executor()};
tc.fn(eng, any_resumable_ref(algo)).validate_error_exact(tc.ec);
BOOST_TEST(eng.stream().calls.size() == 0u);
tc.fn(eng, any_resumable_ref(algo))
.validate_error(tc.ec, create_server_diag("<diagnostics unavailable>"));
BOOST_TEST(eng.value.stream().calls.size() == 0u);
algo.check_calls({
{error_code(), 0u}
});
@ -429,17 +461,16 @@ BOOST_AUTO_TEST_CASE(resume_error_successive_calls)
{
// Setup
mock_algo algo(next_action::connect(), next_action(tc.ec));
asio::io_context ctx;
test_engine eng(ctx.get_executor());
test_engine eng{global_context_executor()};
tc.fn(eng, any_resumable_ref(algo)).validate_error_exact(tc.ec);
BOOST_TEST(eng.stream().calls.size() == 1u);
BOOST_TEST(eng.stream().calls[0].type() == next_action_type::connect);
tc.fn(eng, any_resumable_ref(algo))
.validate_error(tc.ec, create_server_diag("<diagnostics unavailable>"));
BOOST_TEST(eng.value.stream().calls.size() == 1u);
BOOST_TEST(eng.value.stream().calls[0].type() == next_action_type::connect);
algo.check_calls({
{error_code(), 0u},
{error_code(), 0u}
});
// Note: the testing infrastructure checks that we don't do extra posts in the async functions
}
}
}

View File

@ -23,15 +23,15 @@ BOOST_AUTO_TEST_SUITE(test_connection_use_after_move)
using test_connection = connection<test_stream>;
using query_netfun_maker = netfun_maker_mem<void, test_connection, const string_view&, results&>;
using query_netfun_maker = netfun_maker<void, test_connection, const string_view&, results&>;
struct
{
query_netfun_maker::signature query;
const char* name;
} all_fns[] = {
{query_netfun_maker::sync_errc(&test_connection::execute), "sync" },
{query_netfun_maker::async_errinfo(&test_connection::async_execute), "async"},
{query_netfun_maker::sync_errc(&test_connection::execute), "sync" },
{query_netfun_maker::async_diag(&test_connection::async_execute), "async"},
};
BOOST_AUTO_TEST_CASE(use_move_constructed_connection)

View File

@ -22,16 +22,16 @@
#include <boost/mysql/detail/engine_impl.hpp>
#include <boost/mysql/detail/engine_stream_adaptor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/test/unit_test.hpp>
#include <cstdint>
#include <memory>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/buffer_concat.hpp"
#include "test_common/netfun_maker.hpp"
#include "test_common/network_result.hpp"
#include "test_common/printing.hpp"
#include "test_unit/create_coldef_frame.hpp"
#include "test_unit/create_execution_processor.hpp"
@ -42,11 +42,11 @@
#include "test_unit/create_row_message.hpp"
#include "test_unit/create_statement.hpp"
#include "test_unit/fail_count.hpp"
#include "test_unit/run_coroutine.hpp"
#include "test_unit/test_stream.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
namespace asio = boost::asio;
BOOST_AUTO_TEST_SUITE(test_misc)
@ -54,7 +54,6 @@ using test_connection = connection<test_stream>;
// Make sure async_execute() and friends don't cause side
// effects in the initiation
#ifdef BOOST_ASIO_HAS_CO_AWAIT
BOOST_AUTO_TEST_CASE(async_execute_side_effects_in_initiation)
{
test_connection conn;
@ -65,16 +64,13 @@ BOOST_AUTO_TEST_CASE(async_execute_side_effects_in_initiation)
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(2).build()))
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(1).build()));
// Launch coroutine and wait for completion
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
// Call both queries but don't wait on them yet, so they don't initiate
auto aw1 = conn.async_execute("Q1", result1, boost::asio::use_awaitable);
auto aw2 = conn.async_execute("Q2", result2, boost::asio::use_awaitable);
// Create two queries as deferred objects, but don't run them yet
auto q1 = conn.async_execute("Q1", result1, asio::deferred);
auto q2 = conn.async_execute("Q2", result2, asio::deferred);
// Run them in reverse order
co_await std::move(aw2);
co_await std::move(aw1);
});
// Run them in reverse order
std::move(q2)(as_netresult).validate_no_error();
std::move(q1)(as_netresult).validate_no_error();
// Check that we wrote Q2's message first, then Q1's
auto expected = concat_copy(
@ -87,7 +83,6 @@ BOOST_AUTO_TEST_CASE(async_execute_side_effects_in_initiation)
BOOST_TEST(result2.affected_rows() == 2u);
BOOST_TEST(result1.affected_rows() == 1u);
}
#endif // BOOST_ASIO_HAS_CO_AWAIT
// spotcheck for the dynamic interface
// Verifies that execute (dynamic interface) works when rows come in separate batches
@ -148,7 +143,7 @@ BOOST_AUTO_TEST_CASE(execute_multiple_batches)
// Regression check: execute statement with iterator range with a reference type that is convertible to
// field_view, but not equal to field_view
BOOST_AUTO_TEST_CASE(execute_stmt_iterator_reference_not_field_view)
BOOST_AUTO_TEST_CASE(stmt_iterator_reference_not_field_view)
{
results result;
auto stmt = statement_builder().id(1).num_params(2).build();
@ -173,8 +168,6 @@ BOOST_AUTO_TEST_CASE(execute_stmt_iterator_reference_not_field_view)
BOOST_TEST(result.info() == "1st");
}
#ifdef BOOST_ASIO_HAS_CO_AWAIT
// The serialized form of executing a statement with ID=1, params=("test", nullptr)
constexpr std::uint8_t execute_stmt_msg[] = {
0x15, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
@ -187,52 +180,48 @@ BOOST_AUTO_TEST_CASE(async_execute_deferred_lifetimes_rvalues)
{
test_connection conn;
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
results result;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
results result;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
// Deferred op. Execution request is a temporary
auto aw = conn.async_execute(
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
result,
boost::asio::use_awaitable
);
co_await std::move(aw);
// Deferred op. Execution request is a temporary
auto op = conn.async_execute(
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
result,
asio::deferred
);
std::move(op)(as_netresult).validate_no_error();
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(result.info() == "1st");
});
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(result.info() == "1st");
}
BOOST_AUTO_TEST_CASE(async_execute_deferred_lifetimes_lvalues)
{
test_connection conn;
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
results result;
results result;
// Create a bound statement on the heap. This helps tooling detect memory errors
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
auto stmt = statement_builder().id(1).num_params(2).build();
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
// Create a bound statement on the heap. This helps tooling detect memory errors
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
auto stmt = statement_builder().id(1).num_params(2).build();
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
// Messages
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
// Messages
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
// Deferred op
auto aw = conn.async_execute(*stmt_ptr, result, boost::asio::use_awaitable);
// Deferred op
auto op = conn.async_execute(*stmt_ptr, result, asio::deferred);
// Free the statement
stmt_ptr.reset();
// Free the statement
stmt_ptr.reset();
// Actually run the op
co_await std::move(aw);
// Actually run the op
std::move(op)(as_netresult).validate_no_error();
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(result.info() == "1st");
});
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(result.info() == "1st");
}
// Verify that we correctly perform a decay-copy of the parameters and the
@ -241,50 +230,45 @@ BOOST_AUTO_TEST_CASE(async_start_execution_deferred_lifetimes_rvalues)
{
test_connection conn;
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
execution_state st;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
execution_state st;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
// Deferred op. Execution request is a temporary
auto aw = conn.async_start_execution(
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
st,
boost::asio::use_awaitable
);
co_await std::move(aw);
// Deferred op. Execution request is a temporary
auto op = conn.async_start_execution(
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
st,
asio::deferred
);
std::move(op)(as_netresult).validate_no_error();
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(st.info() == "1st");
});
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(st.info() == "1st");
}
BOOST_AUTO_TEST_CASE(deferred_lifetimes_lvalues)
BOOST_AUTO_TEST_CASE(deferred_lifetimes_statement)
{
test_connection conn;
execution_state st;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
execution_state st;
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
// Create a bound statement on the heap. This helps tooling detect memory errors
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
auto stmt = statement_builder().id(1).num_params(2).build();
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
// Create a bound statement on the heap. This helps tooling detect memory errors
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
auto stmt = statement_builder().id(1).num_params(2).build();
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
// Deferred op
auto op = conn.async_start_execution(*stmt_ptr, st, asio::deferred);
// Deferred op
auto aw = conn.async_start_execution(*stmt_ptr, st, boost::asio::use_awaitable);
// Free the statement
stmt_ptr.reset();
// Free the statement
stmt_ptr.reset();
// Actually run the op
std::move(op)(as_netresult).validate_no_error();
// Actually run the op
co_await std::move(aw);
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(st.info() == "1st");
});
// verify that the op had the intended effects
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
BOOST_TEST(st.info() == "1st");
}
// Verify that async_close_statement doesn't require the passed-in statement to be alive. Only
@ -293,58 +277,38 @@ BOOST_AUTO_TEST_CASE(async_close_statement_handle_deferred_tokens)
{
test_connection conn;
run_coroutine(conn.get_executor(), [&]() -> boost::asio::awaitable<void> {
auto stmt = statement_builder().id(3).build();
conn.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
auto stmt = statement_builder().id(3).build();
conn.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
// Deferred op
auto aw = conn.async_close_statement(stmt, boost::asio::use_awaitable);
// Deferred op
auto op = conn.async_close_statement(stmt, asio::deferred);
// Invalidate the original variable
stmt = statement_builder().id(42).build();
// Invalidate the original variable
stmt = statement_builder().id(42).build();
// Run the operation
co_await std::move(aw);
// Run the operation
std::move(op)(as_netresult).validate_no_error();
// verify that the op had the intended effects
const auto expected_message = concat_copy(
create_frame(0, {0x19, 0x03, 0x00, 0x00, 0x00}),
create_frame(0, {0x0e})
);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected_message);
});
}
#endif
// Verify that async functions with signature void(error_code, T)
// get their executor propagated correctly
BOOST_AUTO_TEST_CASE(nonvoid_signature_executor_propagation)
{
// Setup
test_connection conn;
execution_state st;
// Function wrapper. This takes care of validating executor propagation
auto fn = netfun_maker_mem<rows_view, test_connection, execution_state&>::async_errinfo(
&test_connection::async_read_some_rows
// verify that the op had the intended effects
const auto expected_message = concat_copy(
create_frame(0, {0x19, 0x03, 0x00, 0x00, 0x00}),
create_frame(0, {0x0e})
);
// Call it
fn(conn, st).validate_no_error();
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected_message);
}
// Regression check: when there is a network error, sync functions
// returning a value fail with an assertion
BOOST_AUTO_TEST_CASE(net_error_prepare_statement)
{
using netmaker_stmt = netfun_maker_mem<statement, test_connection, string_view>;
using netmaker_stmt = netfun_maker<statement, test_connection, string_view>;
struct
{
const char* name;
netmaker_stmt::signature prepare_statement;
} fns[] = {
{"sync", netmaker_stmt::sync_errc(&test_connection::prepare_statement) },
{"async", netmaker_stmt::async_errinfo(&test_connection::async_prepare_statement)},
{"sync", netmaker_stmt::sync_errc(&test_connection::prepare_statement) },
{"async", netmaker_stmt::async_diag(&test_connection::async_prepare_statement)},
};
for (const auto& fn : fns)
@ -355,21 +319,21 @@ BOOST_AUTO_TEST_CASE(net_error_prepare_statement)
test_connection conn;
conn.stream().set_fail_count(fail_count(0, boost::asio::error::connection_reset));
fn.prepare_statement(conn, "SELECT 1").validate_error_exact(boost::asio::error::connection_reset);
fn.prepare_statement(conn, "SELECT 1").validate_error(boost::asio::error::connection_reset);
}
}
}
BOOST_AUTO_TEST_CASE(net_error_read_some_rows)
{
using netmaker_stmt = netfun_maker_mem<rows_view, test_connection, execution_state&>;
using netmaker_stmt = netfun_maker<rows_view, test_connection, execution_state&>;
struct
{
const char* name;
netmaker_stmt::signature read_some_rows;
} fns[] = {
{"sync", netmaker_stmt::sync_errc(&test_connection::read_some_rows) },
{"async", netmaker_stmt::async_errinfo(&test_connection::async_read_some_rows)},
{"sync", netmaker_stmt::sync_errc(&test_connection::read_some_rows) },
{"async", netmaker_stmt::async_diag(&test_connection::async_read_some_rows)},
};
for (const auto& fn : fns)
@ -382,21 +346,21 @@ BOOST_AUTO_TEST_CASE(net_error_read_some_rows)
execution_state st;
add_meta(get_iface(st), {column_type::bigint});
fn.read_some_rows(conn, st).validate_error_exact(boost::asio::error::connection_reset);
fn.read_some_rows(conn, st).validate_error(boost::asio::error::connection_reset);
}
}
}
BOOST_AUTO_TEST_CASE(net_error_void_signature)
{
using netmaker_execute = netfun_maker_mem<void, test_connection, const string_view&, results&>;
using netmaker_execute = netfun_maker<void, test_connection, const string_view&, results&>;
struct
{
const char* name;
netmaker_execute::signature execute;
} fns[] = {
{"sync", netmaker_execute::sync_errc(&test_connection::execute) },
{"async", netmaker_execute::async_errinfo(&test_connection::async_execute)},
{"sync", netmaker_execute::sync_errc(&test_connection::execute) },
{"async", netmaker_execute::async_diag(&test_connection::async_execute)},
};
for (const auto& fn : fns)
@ -408,7 +372,7 @@ BOOST_AUTO_TEST_CASE(net_error_void_signature)
conn.stream().set_fail_count(fail_count(0, boost::asio::error::connection_reset));
results r;
fn.execute(conn, "SELECT 1", r).validate_error_exact(boost::asio::error::connection_reset);
fn.execute(conn, "SELECT 1", r).validate_error(boost::asio::error::connection_reset);
}
}
}
@ -452,11 +416,8 @@ struct test_any_connection_fixture
}
};
auto pipeline_fn = netfun_maker_mem<
void,
any_connection,
const pipeline_request&,
std::vector<stage_response>&>::async_errinfo(&any_connection::async_run_pipeline);
auto pipeline_fn = netfun_maker<void, any_connection, const pipeline_request&, std::vector<stage_response>&>::
async_diag(&any_connection::async_run_pipeline);
// empty pipelines complete immediately, posting adequately
BOOST_FIXTURE_TEST_CASE(empty_pipeline, test_any_connection_fixture)
@ -483,7 +444,7 @@ BOOST_FIXTURE_TEST_CASE(pipeline_fatal_error, test_any_connection_fixture)
stream().set_fail_count(fail_count(1, boost::asio::error::network_reset));
// Run it
pipeline_fn(conn, req, res).validate_error_exact(boost::asio::error::network_reset);
pipeline_fn(conn, req, res).validate_error(boost::asio::error::network_reset);
// Validate the results
BOOST_TEST(res.size() == 2u);

View File

@ -15,7 +15,6 @@
#include <boost/test/unit_test.hpp>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/check_meta.hpp"
#include "test_common/netfun_maker.hpp"
#include "test_unit/create_coldef_frame.hpp"
@ -33,9 +32,9 @@ BOOST_AUTO_TEST_SUITE(test_multifn)
using test_connection = connection<test_stream>;
using start_query_netm = netfun_maker_mem<void, test_connection, const string_view&, execution_state&>;
using read_resultset_head_netm = netfun_maker_mem<void, test_connection, execution_state&>;
using read_some_rows_netm = netfun_maker_mem<rows_view, test_connection, execution_state&>;
using start_query_netm = netfun_maker<void, test_connection, const string_view&, execution_state&>;
using read_resultset_head_netm = netfun_maker<void, test_connection, execution_state&>;
using read_some_rows_netm = netfun_maker<rows_view, test_connection, execution_state&>;
struct
{
@ -48,9 +47,9 @@ struct
read_resultset_head_netm::sync_errc(&test_connection::read_resultset_head),
read_some_rows_netm::sync_errc(&test_connection::read_some_rows),
"sync" },
{start_query_netm::async_errinfo(&test_connection::async_start_execution),
read_resultset_head_netm::async_errinfo(&test_connection::async_read_resultset_head),
read_some_rows_netm::async_errinfo(&test_connection::async_read_some_rows),
{start_query_netm::async_diag(&test_connection::async_start_execution),
read_resultset_head_netm::async_diag(&test_connection::async_read_resultset_head),
read_some_rows_netm::async_diag(&test_connection::async_read_some_rows),
"async"},
};

View File

@ -37,8 +37,8 @@ using row1 = std::tuple<int, float>;
using row2 = std::tuple<double>;
using state_t = static_execution_state<row1, row1, row2, row1, row2>;
using netfun_maker_row1 = netfun_maker_mem<std::size_t, test_connection, state_t&, span<row1> >;
using netfun_maker_row2 = netfun_maker_mem<std::size_t, test_connection, state_t&, span<row2> >;
using netfun_maker_row1 = netfun_maker<std::size_t, test_connection, state_t&, span<row1> >;
using netfun_maker_row2 = netfun_maker<std::size_t, test_connection, state_t&, span<row2> >;
struct
{
@ -49,8 +49,8 @@ struct
{netfun_maker_row1::sync_errc(&test_connection::read_some_rows),
netfun_maker_row2::sync_errc(&test_connection::read_some_rows),
"sync" },
{netfun_maker_row1::async_errinfo(&test_connection::async_read_some_rows),
netfun_maker_row2::async_errinfo(&test_connection::async_read_some_rows),
{netfun_maker_row1::async_diag(&test_connection::async_read_some_rows),
netfun_maker_row2::async_diag(&test_connection::async_read_some_rows),
"async"},
};
@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(error_row_type_mismatch)
// 1st resultset: row1. Note that this will consume the message
fix.stream().add_bytes(create_text_row_message(0, 10, 4.2f));
fns.read_some_rows_row2(fix.conn, fix.st, fix.storage2)
.validate_error_exact(client_errc::row_type_mismatch);
.validate_error(client_errc::row_type_mismatch);
// Advance resultset
fix.add_ok();
@ -177,7 +177,7 @@ BOOST_AUTO_TEST_CASE(error_row_type_mismatch)
// 3rd resultset: row2
fix.stream().add_bytes(create_text_row_message(1, 9.1));
fns.read_some_rows_row1(fix.conn, fix.st, fix.storage1)
.validate_error_exact(client_errc::row_type_mismatch);
.validate_error(client_errc::row_type_mismatch);
}
}
}