Skip to content

Commit e3d9f42

Browse files
committed
Merge branch 'develop' into feature/366-example-update
2 parents cd3bfbc + 8b2727f commit e3d9f42

File tree

7 files changed

+186
-30
lines changed

7 files changed

+186
-30
lines changed

example/2_simple/pipeline.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ asio::awaitable<std::vector<mysql::statement>> batch_prepare(
5757
for (auto stmt_sql : statements)
5858
req.add_prepare_statement(stmt_sql);
5959

60-
// Run the pipeline. Using as_tuple prevents async_run_pipeline from throwing.
61-
// This allows us to include the diagnostics object diag in the thrown exception.
60+
// Run the pipeline.
6261
// stage_response is a variant-like type that can hold the response of any stage type.
6362
std::vector<mysql::stage_response> pipe_res;
6463
co_await conn.async_run_pipeline(req, pipe_res);

example/Jamfile

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
import os ;
99

10-
project : requirements <library>/boost/mysql//boost_mysql ;
11-
1210
path-constant this_dir : . ;
1311

1412
# The hostname to use for examples

include/boost/mysql/impl/with_diagnostics.hpp

+70-16
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ struct with_diag_handler_fn
5050
std::shared_ptr<diagnostics> owning_diag;
5151
};
5252

53+
// By default, don't modify the signature.
54+
// This makes asio::as_tuple(with_diagnostics(X)) equivalent
55+
// to asio::as_tuple(X).
5356
template <typename Signature>
54-
struct with_diag_signature;
57+
struct with_diag_signature
58+
{
59+
using type = Signature;
60+
};
5561

5662
template <typename R, typename... Args>
5763
struct with_diag_signature<R(error_code, Args...)>
@@ -146,14 +152,23 @@ struct with_diag_init : public Initiation
146152
}
147153
};
148154

149-
} // namespace detail
150-
} // namespace mysql
155+
// Did with_diagnostics modify any of the signatures?
156+
// We really support only modifying all or none, and that's enough.
157+
template <class Signature>
158+
using with_diag_has_original_signature = std::
159+
is_same<Signature, typename with_diag_signature<Signature>::type>;
151160

152-
namespace asio {
161+
template <class... Signatures>
162+
using with_diag_has_original_signatures = mp11::
163+
mp_all_of<mp11::mp_list<Signatures...>, with_diag_has_original_signature>;
153164

165+
template <typename CompletionToken, bool has_original_signatures, typename... Signatures>
166+
struct with_diagnostics_async_result;
167+
168+
// async_result when the signature was modified
154169
template <typename CompletionToken, typename... Signatures>
155-
struct async_result<mysql::with_diagnostics_t<CompletionToken>, Signatures...>
156-
: async_result<CompletionToken, typename mysql::detail::with_diag_signature<Signatures>::type...>
170+
struct with_diagnostics_async_result<CompletionToken, false, Signatures...>
171+
: asio::async_result<CompletionToken, typename with_diag_signature<Signatures>::type...>
157172
{
158173
template <class RawCompletionToken>
159174
using maybe_const_token_t = typename std::conditional<
@@ -163,26 +178,65 @@ struct async_result<mysql::with_diagnostics_t<CompletionToken>, Signatures...>
163178

164179
template <typename Initiation, typename RawCompletionToken, typename... Args>
165180
static auto initiate(Initiation&& initiation, RawCompletionToken&& token, Args&&... args)
166-
-> decltype(async_initiate<
181+
-> decltype(asio::async_initiate<
167182
maybe_const_token_t<RawCompletionToken>,
168-
typename mysql::detail::with_diag_signature<Signatures>::type...>(
169-
std::declval<mysql::detail::with_diag_init<typename std::decay<Initiation>::type>>(),
170-
mysql::detail::access::get_impl(token),
183+
typename with_diag_signature<Signatures>::type...>(
184+
with_diag_init<typename std::decay<Initiation>::type>{std::forward<Initiation>(initiation)},
185+
access::get_impl(token),
171186
std::forward<Args>(args)...
172187
))
173188
{
174-
return async_initiate<
189+
return asio::async_initiate<
175190
maybe_const_token_t<RawCompletionToken>,
176-
typename mysql::detail::with_diag_signature<Signatures>::type...>(
177-
mysql::detail::with_diag_init<typename std::decay<Initiation>::type>{
178-
std::forward<Initiation>(initiation)
179-
},
180-
mysql::detail::access::get_impl(token),
191+
typename with_diag_signature<Signatures>::type...>(
192+
with_diag_init<typename std::decay<Initiation>::type>{std::forward<Initiation>(initiation)},
193+
access::get_impl(token),
181194
std::forward<Args>(args)...
182195
);
183196
}
184197
};
185198

199+
// async_result when the signature wasn't modified (pass-through)
200+
template <typename CompletionToken, typename... Signatures>
201+
struct with_diagnostics_async_result<CompletionToken, true, Signatures...>
202+
: asio::async_result<CompletionToken, Signatures...>
203+
{
204+
template <class RawCompletionToken>
205+
using maybe_const_token_t = typename std::conditional<
206+
std::is_const<typename std::remove_reference<RawCompletionToken>::type>::value,
207+
const CompletionToken,
208+
CompletionToken>::type;
209+
210+
template <typename Initiation, typename RawCompletionToken, typename... Args>
211+
static auto initiate(Initiation&& initiation, RawCompletionToken&& token, Args&&... args)
212+
-> decltype(asio::async_initiate<maybe_const_token_t<RawCompletionToken>, Signatures...>(
213+
std::forward<Initiation>(initiation),
214+
access::get_impl(token),
215+
std::forward<Args>(args)...
216+
))
217+
{
218+
return asio::async_initiate<maybe_const_token_t<RawCompletionToken>, Signatures...>(
219+
std::forward<Initiation>(initiation),
220+
access::get_impl(token),
221+
std::forward<Args>(args)...
222+
);
223+
}
224+
};
225+
226+
} // namespace detail
227+
} // namespace mysql
228+
229+
namespace asio {
230+
231+
template <typename CompletionToken, typename... Signatures>
232+
struct async_result<mysql::with_diagnostics_t<CompletionToken>, Signatures...>
233+
: mysql::detail::with_diagnostics_async_result<
234+
CompletionToken,
235+
mysql::detail::with_diag_has_original_signatures<Signatures...>::value,
236+
Signatures...>
237+
{
238+
};
239+
186240
} // namespace asio
187241
} // namespace boost
188242

include/boost/mysql/with_diagnostics.hpp

+13-7
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,26 @@ namespace mysql {
2424
* Uses knowledge of Boost.MySQL internals to grab any \ref diagnostics
2525
* that the operation may produce to create a `std::exception_ptr` pointing to an \ref error_with_diagnostics
2626
* object. On success, the generated `std::exception_ptr` will be `nullptr`.
27-
* \n
27+
*
2828
* Using `with_diagnostics` to wrap tokens that throw exceptions (like `deferred` + `co_await` or
2929
* `yield_context`) enhances the thrown exceptions with diagnostics information, matching the ones thrown by
3030
* sync functions. If you don't use this token, Asio will use `system_error` exceptions, containing less info.
31-
* \n
31+
*
3232
* This token can only be used with operations involving Boost.MySQL, as it relies on its internals.
33-
* \n
33+
*
3434
* Like `asio::as_tuple`, this class wraps another completion token. For instance,
3535
* `with_diagnostics(asio::deferred)` will generate a deferred operation with an adapted
3636
* signature, which will throw `error_with_diagnostics` when `co_await`'ed.
37-
* \n
38-
* This token can only be used for operations with `void(error_code, T...)` signature.
39-
* If this precondition is broken, a compile-time error will be issued. In particular,
40-
* `with_diagnostics(asio::as_tuple(X))` is an error.
37+
*
38+
* If this token is applied to a function with a handler signature that
39+
* does not match `void(error_code, T...)`, the token acts as a pass-through:
40+
* it does not modify the signature, and calls the underlying token's initiation directly.
41+
* This has the following implications:
42+
*
43+
* - `asio::as_tuple(with_diagnostics(X))` is equivalent to `asio::as_tuple(X)`.
44+
* - `asio::redirect_error(with_diagnostics(X))` is equivalent to `asio::redirect_error(X)`.
45+
* - Tokens like `asio::as_tuple` and `asio::redirect_error` can be used as partial tokens
46+
* when `with_diagnostics` is the default completion token, as is the case for \ref any_connection.
4147
*/
4248
template <class CompletionToken>
4349
class with_diagnostics_t

test/Jamfile

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import ac ;
1515
import indirect ;
1616
import config : requires ;
1717

18-
project : requirements <library>/boost/mysql//boost_mysql ;
19-
2018
# Support header-only builds
2119
feature.feature boost.mysql.separate-compilation : on off : propagated composite ;
2220

@@ -67,6 +65,7 @@ local requirements =
6765
<toolset>msvc:<define>_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING
6866
<toolset>msvc:<define>_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING
6967
<toolset>msvc:<define>_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
68+
<toolset>msvc:<define>_CRT_SECURE_NO_WARNINGS # Required by Asio
7069
# gcc-13+ doesn't understand view types and issues array bound warnings that don't make sense.
7170
# -Wno-implicit-fallthrough is required by Asio SSL components
7271
<toolset>gcc:<cxxflags>"-Wno-dangling-reference -Wno-array-bounds -Wno-implicit-fallthrough"
@@ -90,7 +89,7 @@ local requirements =
9089

9190
alias boost_mysql
9291
:
93-
/boost/charconv//boost_charconv
92+
$(boost_dependencies)/<warnings-as-errors>off
9493
/openssl//ssl/<link>shared
9594
/openssl//crypto/<link>shared
9695
: requirements

test/integration/test/any_connection.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222

2323
#include <boost/mysql/impl/internal/variant_stream.hpp>
2424

25+
#include <boost/asio/as_tuple.hpp>
2526
#include <boost/asio/awaitable.hpp>
2627
#include <boost/asio/cancel_after.hpp>
2728
#include <boost/asio/co_spawn.hpp>
2829
#include <boost/asio/error.hpp>
2930
#include <boost/asio/local/basic_endpoint.hpp>
31+
#include <boost/asio/redirect_error.hpp>
3032
#include <boost/test/data/test_case.hpp>
3133

3234
#include <chrono>
@@ -317,6 +319,47 @@ BOOST_FIXTURE_TEST_CASE(default_token_cancel_after, any_connection_fixture)
317319
});
318320
}
319321

322+
// Using as_tuple as partial token works
323+
BOOST_FIXTURE_TEST_CASE(default_token_as_tuple, any_connection_fixture)
324+
{
325+
run_coro(ctx, [&]() -> asio::awaitable<void> {
326+
// connect
327+
auto [ec] = co_await conn.async_connect(connect_params_builder().build(), asio::as_tuple);
328+
BOOST_TEST_REQUIRE(ec == error_code());
329+
330+
// Returning a value works
331+
auto [ec2, stmt] = co_await conn.async_prepare_statement("SELECT ?", asio::as_tuple);
332+
BOOST_TEST_REQUIRE(ec2 == error_code());
333+
BOOST_TEST(stmt.valid());
334+
335+
// Error case
336+
results result;
337+
auto [ec3] = co_await conn.async_execute("SELECT * FROM bad_table", result, asio::as_tuple);
338+
BOOST_TEST(ec3 == common_server_errc::er_no_such_table);
339+
});
340+
}
341+
342+
// Using redirect_error as partial token works
343+
BOOST_FIXTURE_TEST_CASE(default_token_redirect_error, any_connection_fixture)
344+
{
345+
run_coro(ctx, [&]() -> asio::awaitable<void> {
346+
// connect
347+
error_code ec;
348+
co_await conn.async_connect(connect_params_builder().build(), asio::redirect_error(ec));
349+
BOOST_TEST_REQUIRE(ec == error_code());
350+
351+
// Returning a value works
352+
auto stmt = co_await conn.async_prepare_statement("SELECT ?", asio::redirect_error(ec));
353+
BOOST_TEST_REQUIRE(ec == error_code());
354+
BOOST_TEST(stmt.valid());
355+
356+
// Error case
357+
results result;
358+
co_await conn.async_execute("SELECT * FROM bad_table", result, asio::redirect_error(ec));
359+
BOOST_TEST(ec == common_server_errc::er_no_such_table);
360+
});
361+
}
362+
320363
// Spotcheck: immediate completions dispatched to the immediate executor
321364
BOOST_FIXTURE_TEST_CASE(immediate_completions, any_connection_fixture)
322365
{

test/unit/test/with_diagnostics.cpp

+57
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,61 @@ static_assert(
305305
""
306306
);
307307

308+
// Applying with_diagnostics to an unkown signature is a pass-through
309+
struct no_ec_initiation
310+
{
311+
template <class Handler, class T1, class T2, class T3>
312+
void operator()(Handler&& handler, T1&& arg1, T2&& arg2, T3&& arg3)
313+
{
314+
// T1 should be a non-const lvalue
315+
static_assert(std::is_same<T1, std::shared_ptr<int>&>::value, "");
316+
BOOST_TEST(arg1 != nullptr);
317+
318+
// T2 should be a const lvalue
319+
static_assert(std::is_same<T2, const std::shared_ptr<int>&>::value, "");
320+
BOOST_TEST(arg2 != nullptr);
321+
322+
// T3 should be a rvalue
323+
static_assert(std::is_same<T3, std::shared_ptr<int>>::value, "");
324+
BOOST_TEST(arg3 != nullptr);
325+
auto arg3_move = std::move(arg3);
326+
boost::ignore_unused(arg3_move);
327+
328+
// Just call the handler
329+
std::move(handler)(42);
330+
}
331+
};
332+
333+
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(int)) CompletionToken>
334+
void async_no_ec(
335+
std::shared_ptr<int>& arg1,
336+
const std::shared_ptr<int>& arg2,
337+
std::shared_ptr<int>&& arg3,
338+
CompletionToken&& token
339+
)
340+
{
341+
asio::async_initiate<CompletionToken, void(int)>(no_ec_initiation{}, token, arg1, arg2, std::move(arg3));
342+
}
343+
344+
BOOST_AUTO_TEST_CASE(signature_no_ec)
345+
{
346+
// Setup
347+
auto arg1 = std::make_shared<int>(42);
348+
auto arg2 = arg1;
349+
auto arg3 = arg1;
350+
bool called = false;
351+
auto handler = [&](int val) {
352+
BOOST_TEST(val == 42);
353+
called = true;
354+
};
355+
356+
// Call the operation
357+
async_no_ec(arg1, arg2, std::move(arg3), with_diagnostics(handler));
358+
359+
// lvalues not moved, rvalue moved
360+
BOOST_TEST(arg1 != nullptr);
361+
BOOST_TEST(arg2 != nullptr);
362+
BOOST_TEST(arg3 == nullptr);
363+
}
364+
308365
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)