Pipeline API

Removed sansio_algorithm base class

close #75
This commit is contained in:
Anarthal (Rubén Pérez)
2024-06-08 12:47:42 +02:00
committed by GitHub
parent 22eddece25
commit 9ba4f9dfed
101 changed files with 6341 additions and 1632 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeIsMainRegex: "([-_](test|unittest))?$"
IncludeIsMainRegex: null
IncludeIsMainSourceRegex: ""
IndentGotoLabels: true
IndentPPDirectives: None
+1 -1
View File
@@ -291,9 +291,9 @@ def main(ctx):
linux_b2('Linux B2 clang-14-arm64', _image('build-clang14'), toolset='clang-14', cxxstd='20', arch='arm64'),
linux_b2('Linux B2 clang-16-sanit', _image('build-clang16'), toolset='clang-16', cxxstd='20', address_sanitizer=1, undefined_sanitizer=1),
linux_b2('Linux B2 clang-16-i386-sanit', _image('build-clang16-i386'), toolset='clang-16', cxxstd='20', address_model=32, address_sanitizer=1, undefined_sanitizer=1),
linux_b2('Linux B2 gcc-5', _image('build-gcc5'), toolset='gcc-5', cxxstd='11'), # gcc-5 C++14 doesn't like my constexpr field_view
linux_b2('Linux B2 clang-17', _image('build-clang17'), toolset='clang-17', cxxstd='20'),
linux_b2('Linux B2 clang-18', _image('build-clang18'), toolset='clang-18', cxxstd='23'),
linux_b2('Linux B2 gcc-5', _image('build-gcc5'), toolset='gcc-5', cxxstd='11'), # gcc-5 C++14 doesn't like my constexpr field_view
linux_b2('Linux B2 gcc-5-ts-executor', _image('build-gcc5'), toolset='gcc-5', cxxstd='11', use_ts_executor=1),
linux_b2('Linux B2 gcc-6', _image('build-gcc6'), toolset='gcc-6', cxxstd='14,17'),
linux_b2('Linux B2 gcc-10', _image('build-gcc10'), toolset='gcc-10', cxxstd='17,20'),
+4
View File
@@ -64,8 +64,12 @@ docca.reference reference.qbk
\"BOOST_MYSQL_OUTPUT_STRING=class\" \\
\"BOOST_MYSQL_FORMATTABLE=class\" \\
\"BOOST_MYSQL_STATIC_ROW=class\" \\
\"BOOST_MYSQL_PIPELINE_REQUEST_TYPE=class\" \\
\"BOOST_MYSQL_PIPELINE_STAGE_TYPE=class\" \\
\"BOOST_MYSQL_WRITABLE_FIELD=class\" \\
\"BOOST_MYSQL_DECL=\" \\
\"BOOST_MYSQL_HAS_LOCAL_TIME=\" \\
\"BOOST_NO_CXX17_DEDUCTION_GUIDES=\" \\
"
<doxygen:param>SKIP_FUNCTION_MACROS=NO
<doxygen:param>OUTPUT_LANGUAGE=English
+7
View File
@@ -42,10 +42,13 @@
[def __FieldViewFwdIterator__ [reflink2 FieldViewFwdIterator ['FieldViewFwdIterator]]]
[def __Formattable__ [reflink2 Formattable ['Formattable]]]
[def __OutputString__ [reflink2 OutputString ['OutputString]]]
[def __PipelineRequestType__ [reflink2 PipelineRequestType ['PipelineRequestType]]]
[def __PipelineStageType__ [reflink2 PipelineStageType ['PipelineStageType]]]
[def __ResultsType__ [reflink2 ResultsType ['ResultsType]]]
[def __SocketStream__ [reflink2 SocketStream ['SocketStream]]]
[def __StaticRow__ [reflink2 StaticRow ['StaticRow]]]
[def __Stream__ [reflink2 Stream ['Stream]]]
[def __WritableField__ [reflink2 WritableFieldTuple ['WritableField]]]
[def __WritableFieldTuple__ [reflink2 WritableFieldTuple ['WritableFieldTuple]]]
@@ -136,6 +139,7 @@ END
[import ../../test/integration/test/snippets/any_connection.cpp]
[import ../../test/integration/test/snippets/connection_pool.cpp]
[import ../../test/integration/test/snippets/sql_formatting.cpp]
[import ../../test/integration/test/snippets/pipeline.cpp]
[include 01_intro.qbk]
[include 02_integrating.qbk]
@@ -160,6 +164,7 @@ END
[include 21_connection_pool.qbk]
[include 22_sql_formatting.qbk]
[include 23_sql_formatting_advanced.qbk]
[include 24_pipeline.qbk]
[include 24_examples.qbk]
[include 25_tests.qbk]
@@ -173,6 +178,8 @@ END
[include helpers/FieldViewFwdIterator.qbk]
[include helpers/Formattable.qbk]
[include helpers/OutputString.qbk]
[include helpers/PipelineRequestType.qbk]
[include helpers/PipelineStageType.qbk]
[include helpers/ResultsType.qbk]
[include helpers/SocketStream.qbk]
[include helpers/StaticRow.qbk]
+24
View File
@@ -31,6 +31,7 @@ Here is a list of available examples:
# [link mysql.examples.batch_inserts_generic (Experimental) Generic batch inserts with Boost.Describe: extending format_sql]
# [link mysql.examples.dynamic_filters (Experimental) Implements a query with several dynamic filters using client-side query formatting]
# [link mysql.examples.patch_updates (Experimental) Implements a dynamic UPDATE query for PATCH-like update semantics using client-side query formatting]
# [link mysql.examples.pipeline (Experimental) Prepares, executes and closes statements in batch using the pipeline API]
# [link mysql.examples.connection_pool (Experimental) A REST API server that uses connection pooling]
# [@https://github.com/anarthal/servertech-chat The BoostServerTech chat project uses Boost.MySQL and Boost.Redis to implement a chat server]
@@ -399,6 +400,29 @@ __assume_setup__
[section:pipeline (Experimental) Pipelines]
This example demonstrates how use [link mysql.pipeline the pipeline API]
to prepare, execute and close statements in batch.
The example employs async functions with C++20 coroutines.
__assume_setup__
[import ../../example/pipeline.cpp]
[example_pipeline]
[endsect]
[section:connection_pool (Experimental) Connection pools]
This example demonstrates how to use [reflink connection_pool].
+201
View File
@@ -0,0 +1,201 @@
[/
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)
]
[section:pipeline (Experimental) Pipelines]
[nochunk]
Functions like [refmemunq any_connection execute], [refmemunq any_connection prepare_statement]
and their async counterparts are half-duplex:
they write a single request to the server and wait for its response.
In contrast, pipelines can increase efficiency by coalescing several requests into a single message, saving round-trips to the server.
[warning
The MySQL client/server protocol doesn't have explicit support for pipelines. [*From the server's point of view,
a pipeline is just a sequence of unrelated requests]. The server will try to execute all stages
in each pipeline, regardless of the result of previous stages. Pipelines are considered
an [*advanced feature]. Please read [link mysql.pipeline.pitfalls the pitfalls section] for more info.
]
[note
This feature is experimental. Its API may change in subsequent releases.
]
[heading Use cases]
You should use pipelines for lightweight operations, dominated by round-trip time. Typical examples include:
* Running connection setup code, involving operations like [refmemunq any_connection reset_connection],
[refmemunq any_connection set_character_set] or preparing statements. [reflink connection_pool] uses
pipelines to clean up connections for re-use.
* Preparing several statements, in batch.
* Executing and closing a statement in a single round-trip.
You should [*avoid] pipelines for the following cases:
* When you can achieve the same functionality using semicolon-separated queries
(thus using [link mysql.multi_resultset.multi_queries multi-queries] and [link mysql.sql_formatting client-side SQL formatting]).
Multi-queries will stop after the first error, which is usually what you want. See [link mysql.pipeline.pitfalls this section] for more info.
* When running heavyweight queries, where the gains in round-trip time are not significant.
* When there are dependencies between stages in the pipeline. Lack of protocol support makes this use case impossible.
If you're not sure, don't use this feature.
[heading Pipeline requests and responses]
There are two interfaces to pipelines: dynamic ([reflink pipeline_request]) and static
([reflink static_pipeline_request]). Both are C++11 compatible.
To run a dynamic pipeline, create a request object describing what should the pipeline do:
[pipeline_dynamic_request]
Use [refmem pipeline_request add] with any of the [link mysql.pipeline.reference available stage types]
to add stages your pipeline.
To actually run the pipeline, create a response object and call
[refmem any_connection run_pipeline] or [refmemunq any_connection async_run_pipeline]:
[pipeline_dynamic_run]
Finally, you can access the statements using:
[pipeline_dynamic_results]
[heading Static pipelines]
If the type and number of stages in your pipeline is known at compile-time, you can use
static pipelines, instead. The mechanics are similar, except that some checks are moved
to compile-time. The pipeline in the previous example can be rewritten as:
[pipeline_static]
[heading:error Error handling]
If any of the pipeline stages result in an error, the entire [refmemunq any_connection run_pipeline] operation
is considered failed. This means that [*if `run_pipipeline` completed successfully, all stages succeeded]. Recall that
[*all stages are always run, regardless of the outcome of previous stages].
If `run_pipipeline` fails, you can check which stages succeeded and failed by inspecting responses.
Response types like [refmem prepare_statement_stage response_type] are aliases for
[link mysql.error_handling.system_result `boost::system::result`], a vocabulary type that
can contain either a value or an error. For instance:
[pipeline_errors]
The module uses [reflink errcode_with_diagnostics] as the error type,
which contains an `error_code` and a [reflink diagnostics] object.
[heading:pitfalls Potential pitfalls]
All requests in the pipeline are always run, regardless of the outcome of previous requests. As a result, some pipelines can behave non-intuitively:
[pipeline_pitfalls_bad]
Pipelines aren't the best fit here. Instead, you can express the same logic using semicolon-separated queries:
[pipeline_pitfalls_good]
Pipeline stages are run sequentially by the server. If any of the stages involves a heavyweight query,
the server won't process subsequent stages until the query completes.
[heading:reference Pipeline stage reference]
In the table below, the following variables are assumed:
* `req` is a [reflink pipeline_request].
* `stmt` is a valid [reflink statement].
* `result` is a [reflink results] object.
* `conn` is an [reflink any_connection] object.
[table:reference
[
[Stage type]
[Example]
[When run, equivalent to...]
[Response type]
]
[
[
[reflink execute_stage][br][br]
Behaves like [refmem any_connection execute]
]
[[pipeline_reference_execute]]
[[pipeline_reference_execute_equivalent]]
[
[reflink results] or [reflink errcode_with_diagnostics]
]
]
[
[
[reflink prepare_statement_stage][br][br]
Behaves like [refmem any_connection prepare_statement]
]
[[pipeline_reference_prepare_statement]]
[[pipeline_reference_prepare_statement_equivalent]]
[
[reflink statement] or [reflink errcode_with_diagnostics]
]
]
[
[
[reflink close_statement_stage][br][br]
Behaves like [refmem any_connection close_statement]
]
[[pipeline_reference_close_statement]]
[[pipeline_reference_close_statement_equivalent]]
[
Possibly empty [reflink errcode_with_diagnostics]
]
]
[
[
[reflink reset_connection_stage][br][br]
Behaves like [refmem any_connection reset_connection]
]
[[pipeline_reference_reset_connection]]
[[pipeline_reference_reset_connection_equivalent]]
[
Possibly empty [reflink errcode_with_diagnostics]
]
]
[
[
[reflink set_character_set_stage][br][br]
Behaves like [refmem any_connection set_character_set]
]
[[pipeline_reference_set_character_set]]
[[pipeline_reference_set_character_set_equivalent]]
[
Possibly empty [reflink errcode_with_diagnostics]
]
]
]
[endsect]
+15
View File
@@ -0,0 +1,15 @@
[/
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)
]
[section:boost__mysql__PipelineRequestType PipelineRequestType concept]
A type `T` satisfies `PipelineRequestType` if either:
* It's exactly the [reflink pipeline_request] class.
* It's an instantiation of the [reflink static_pipeline_request] template class.
[endsect]
+18
View File
@@ -0,0 +1,18 @@
[/
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)
]
[section:boost__mysql__PipelineStageType PipelineStageType concept]
A type `T` satisfies `PipelineStageType` if it's exactly one of these types:
* [reflink execute_stage]
* [reflink prepare_statement_stage]
* [reflink close_statement_stage]
* [reflink reset_connection_stage]
* [reflink set_character_set_stage]
[endsect]
+13
View File
@@ -18,12 +18,14 @@
<member><link linkend="mysql.ref.boost__mysql__any_address">any_address</link></member>
<member><link linkend="mysql.ref.boost__mysql__any_connection">any_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__any_connection_params">any_connection_params</link></member>
<member><link linkend="mysql.ref.boost__mysql__any_stage_response">any_stage_response</link></member>
<member><link linkend="mysql.ref.boost__mysql__bad_field_access">bad_field_access</link></member>
<member><link linkend="mysql.ref.boost__mysql__basic_format_context">basic_format_context</link></member>
<member><link linkend="mysql.ref.boost__mysql__bound_statement_tuple">bound_statement_tuple</link></member>
<member><link linkend="mysql.ref.boost__mysql__bound_statement_iterator_range">bound_statement_iterator_range</link></member>
<member><link linkend="mysql.ref.boost__mysql__buffer_params">buffer_params</link></member>
<member><link linkend="mysql.ref.boost__mysql__character_set">character_set</link></member>
<member><link linkend="mysql.ref.boost__mysql__close_statement_stage">close_statement_stage</link></member>
<member><link linkend="mysql.ref.boost__mysql__connect_params">connect_params</link></member>
<member><link linkend="mysql.ref.boost__mysql__connection">connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__connection_pool">connection_pool</link></member>
@@ -31,7 +33,9 @@
<member><link linkend="mysql.ref.boost__mysql__date">date</link></member>
<member><link linkend="mysql.ref.boost__mysql__datetime">datetime</link></member>
<member><link linkend="mysql.ref.boost__mysql__diagnostics">diagnostics</link></member>
<member><link linkend="mysql.ref.boost__mysql__errcode_with_diagnostics">errcode_with_diagnostics</link></member>
<member><link linkend="mysql.ref.boost__mysql__error_with_diagnostics">error_with_diagnostics</link></member>
<member><link linkend="mysql.ref.boost__mysql__execute_stage">execute_stage</link></member>
<member><link linkend="mysql.ref.boost__mysql__execution_state">execution_state</link></member>
<member><link linkend="mysql.ref.boost__mysql__field">field</link></member>
<member><link linkend="mysql.ref.boost__mysql__field_view">field_view</link></member>
@@ -45,9 +49,12 @@
<member><link linkend="mysql.ref.boost__mysql__metadata">metadata</link></member>
<member><link linkend="mysql.ref.boost__mysql__pfr_by_name">pfr_by_name</link></member>
<member><link linkend="mysql.ref.boost__mysql__pfr_by_position">pfr_by_position</link></member>
<member><link linkend="mysql.ref.boost__mysql__pipeline_request">pipeline_request</link></member>
<member><link linkend="mysql.ref.boost__mysql__pool_executor_params">pool_executor_params</link></member>
<member><link linkend="mysql.ref.boost__mysql__pool_params">pool_params</link></member>
<member><link linkend="mysql.ref.boost__mysql__pooled_connection">pooled_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__prepare_statement_stage">prepare_statement_stage</link></member>
<member><link linkend="mysql.ref.boost__mysql__reset_connection_stage">reset_connection_stage</link></member>
<member><link linkend="mysql.ref.boost__mysql__results">results</link></member>
<member><link linkend="mysql.ref.boost__mysql__resultset_view">resultset_view</link></member>
<member><link linkend="mysql.ref.boost__mysql__resultset">resultset</link></member>
@@ -55,8 +62,10 @@
<member><link linkend="mysql.ref.boost__mysql__row_view">row_view</link></member>
<member><link linkend="mysql.ref.boost__mysql__rows">rows</link></member>
<member><link linkend="mysql.ref.boost__mysql__rows_view">rows_view</link></member>
<member><link linkend="mysql.ref.boost__mysql__set_character_set_stage">set_character_set_stage</link></member>
<member><link linkend="mysql.ref.boost__mysql__statement">statement</link></member>
<member><link linkend="mysql.ref.boost__mysql__static_execution_state">static_execution_state</link></member>
<member><link linkend="mysql.ref.boost__mysql__static_pipeline_request">static_pipeline_request</link></member>
<member><link linkend="mysql.ref.boost__mysql__static_results">static_results</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_path">unix_path</link></member>
</simplelist>
@@ -97,6 +106,7 @@
<member><link linkend="mysql.ref.boost__mysql__get_mysql_server_category">get_mysql_server_category</link></member>
<member><link linkend="mysql.ref.boost__mysql__get_mariadb_server_category">get_mariadb_server_category</link></member>
<member><link linkend="mysql.ref.boost__mysql__make_error_code">make_error_code</link></member>
<member><link linkend="mysql.ref.boost__mysql__make_pipeline_request">make_pipeline_request</link></member>
<member><link linkend="mysql.ref.boost__mysql__runtime">runtime</link></member>
<member><link linkend="mysql.ref.boost__mysql__throw_on_error">throw_on_error</link></member>
</simplelist>
@@ -125,6 +135,8 @@
<member><link linkend="mysql.ref.boost__mysql__FieldViewFwdIterator">FieldViewFwdIterator</link></member>
<member><link linkend="mysql.ref.boost__mysql__Formattable">Formattable</link></member>
<member><link linkend="mysql.ref.boost__mysql__OutputString">OutputString</link></member>
<member><link linkend="mysql.ref.boost__mysql__PipelineRequestType">PipelineRequestType</link></member>
<member><link linkend="mysql.ref.boost__mysql__PipelineStageType">PipelineStageType</link></member>
<member><link linkend="mysql.ref.boost__mysql__ResultsType">ResultsType</link></member>
<member><link linkend="mysql.ref.boost__mysql__SocketStream">SocketStream</link></member>
<member><link linkend="mysql.ref.boost__mysql__StaticRow">StaticRow</link></member>
@@ -138,6 +150,7 @@
<member><link linkend="mysql.static_interface.readable_field_reference">ReadableField types</link></member>
<member><link linkend="mysql.prepared_statements.writable_field_reference">WritableField types</link></member>
<member><link linkend="mysql.sql_formatting.reference">Formattable types</link></member>
<member><link linkend="mysql.pipeline.reference">Pipeline stage reference</link></member>
<member><link linkend="mysql.charsets.string_encoding">String encoding</link></member>
</simplelist>
</entry>
+3
View File
@@ -24,10 +24,13 @@
'FieldViewFwdIterator',
'Formattable',
'OutputString',
'PipelineRequestType',
'PipelineStageType',
'ResultsType',
'SocketStream',
'StaticRow',
'Stream',
'WritableField',
'WritableFieldTuple'
"/>
+1
View File
@@ -77,6 +77,7 @@ set(REGULAR_EXAMPLES
metadata
ssl
timeouts
pipeline
)
foreach(FILE_NAME ${REGULAR_EXAMPLES})
+1
View File
@@ -58,6 +58,7 @@ run_regular_example async_futures ;
run_regular_example metadata ;
run_regular_example ssl ;
run_regular_example timeouts ;
run_regular_example pipeline ;
run_regular_example async_coroutines : /boost/mysql/test//boost_context_lib ;
run_regular_example any_connection : /boost/mysql/test//boost_context_lib ;
+1 -1
View File
@@ -212,7 +212,7 @@ void main_impl(int argc, char** argv)
params.username = args.username;
params.password = args.password;
params.database = "boost_mysql_examples";
params.multi_queries = true; // TODO
params.multi_queries = true;
// Connect to the server
conn.connect(params);
+225
View File
@@ -0,0 +1,225 @@
//
// 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)
//
//[example_pipeline
// This example demonstrates how to use the pipeline API to prepare,
// execute and close statements in batch.
// It uses asynchronous functions and C++20 coroutines (with boost::asio::co_spawn).
//
// Pipelines are an experimental feature.
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/throw_on_error.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/core/span.hpp>
#include <array>
#include <iostream>
#include <vector>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
namespace asio = boost::asio;
using boost::mysql::error_code;
using boost::mysql::string_view;
// Prepare several statements in batch.
// This is faster than preparing them one by one, as it saves round-trips to the server.
asio::awaitable<std::vector<boost::mysql::statement>> batch_prepare(
boost::mysql::any_connection& conn,
boost::span<const string_view> statements
)
{
// Construct a pipeline request describing the work to be performed.
// There must be one prepare_statement_stage per statement to prepare
boost::mysql::pipeline_request req;
for (auto stmt_sql : statements)
req.add(boost::mysql::prepare_statement_stage(stmt_sql));
// Run the pipeline. Using as_tuple prevents async_run_pipeline from throwing.
// This allows us to include the diagnostics object diag in the thrown exception.
// any_stage_response is a variant-like type that can hold the response of any stage type.
std::vector<boost::mysql::any_stage_response> pipe_res;
boost::mysql::diagnostics diag;
auto [ec] = co_await conn.async_run_pipeline(req, pipe_res, diag, asio::as_tuple(asio::deferred));
boost::mysql::throw_on_error(ec, diag);
// If we got here, all statements were prepared successfully.
// pipe_res contains as many elements as statements.size(), holding statement objects
// Extract them into a vector
std::vector<boost::mysql::statement> res;
res.reserve(statements.size());
for (const auto& stage_res : pipe_res)
res.push_back(stage_res.get_statement());
co_return res;
}
void main_impl(int argc, char** argv)
{
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
const char* hostname = argv[3];
// The company_id to use when inserting new employees.
// This is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
// I/O context
boost::asio::io_context ctx;
// Connection. Note that the connection's type doesn't depend
// on the transport (TCP or UNIX sockets).
boost::mysql::any_connection conn(ctx);
// Connection configuration. This contains the server address,
// credentials, and other configuration used during connection establishment.
// Note that, by default, TCP connections will use TLS. connect_params::ssl
// allows disabling it.
boost::mysql::connect_params params;
// The server address. This can either be a host and port or a UNIX socket path
params.server_address.emplace_host_and_port(hostname);
// Username to log in as
params.username = argv[1];
// Password to use
params.password = argv[2];
// Database to use; leave empty or omit for no database
params.database = "boost_mysql_examples";
// Spawn a coroutine running the passed function
boost::asio::co_spawn(
ctx.get_executor(),
[&conn, &params, company_id]() -> boost::asio::awaitable<void> {
// Use as_tuple and throw_on_error to include diagnostics in our exceptions
constexpr auto tok = boost::asio::as_tuple(boost::asio::deferred);
boost::mysql::diagnostics diag;
// Connect to the server
auto [ec] = co_await conn.async_connect(params, diag, tok);
boost::mysql::throw_on_error(ec, diag);
// Prepare the statements using the batch prepare function that we previously defined
const std::array<string_view, 2> stmt_sql{
"INSERT INTO employee (company_id, first_name, last_name) VALUES (?, ?, ?)",
"INSERT INTO audit_log (msg) VALUES (?)"
};
std::vector<boost::mysql::statement> stmts = co_await batch_prepare(conn, stmt_sql);
// Create a request to execute them, this time using the static interface.
// Warning: do NOT include the COMMIT statement in this pipeline.
// COMMIT must only be executed if all the previous statements succeeded.
// In a pipeline, all stages get executed, regardless of the outcome of previous stages.
// We say that COMMIT has a dependency on the result of previous stages.
boost::mysql::static_pipeline_request req(
boost::mysql::execute_stage("START TRANSACTION"),
boost::mysql::execute_stage(stmts.at(0), {company_id, "Juan", "Lopez"}),
boost::mysql::execute_stage(stmts.at(0), {company_id, "Pepito", "Rodriguez"}),
boost::mysql::execute_stage(stmts.at(0), {company_id, "Someone", "Random"}),
boost::mysql::execute_stage(stmts.at(1), {"Inserted 3 new emplyees"})
);
decltype(req)::response_type res;
// Execute the static pipeline
std::tie(ec) = co_await conn.async_run_pipeline(req, res, diag, tok);
boost::mysql::throw_on_error(ec, diag);
// If we got here, all stages executed successfully.
// Since they were execution stages, the response contains a results object.
// Get the IDs of the newly created employees
auto id1 = std::get<1>(res)->last_insert_id();
auto id2 = std::get<2>(res)->last_insert_id();
auto id3 = std::get<3>(res)->last_insert_id();
// We can now commit our transaction and close the statements.
// Create the request to do so
boost::mysql::static_pipeline_request pipe2{
boost::mysql::execute_stage("COMMIT"),
boost::mysql::close_statement_stage(stmts.at(0)),
boost::mysql::close_statement_stage(stmts.at(1))
};
decltype(pipe2)::response_type res2;
// Run it
std::tie(ec) = co_await conn.async_run_pipeline(pipe2, res2, diag, tok);
boost::mysql::throw_on_error(ec, diag);
// If we got here, our insertions got committed.
std::cout << "Inserted employees: " << id1 << ", " << id2 << ", " << id3 << std::endl;
// Notify the MySQL server we want to quit, then close the underlying connection.
std::tie(ec) = co_await conn.async_close(diag, tok);
boost::mysql::throw_on_error(ec, diag);
},
// If any exception is thrown in the coroutine body, rethrow it.
[](std::exception_ptr ptr) {
if (ptr)
{
std::rethrow_exception(ptr);
}
}
);
// Don't forget to call run()! Otherwise, your program
// will not spawn the coroutine and will do nothing.
ctx.run();
}
int main(int argc, char** argv)
{
try
{
main_impl(argc, argv);
}
catch (const boost::mysql::error_with_diagnostics& err)
{
// You will only get this type of exceptions if you use throw_on_error.
// Some errors include additional diagnostics, like server-provided error messages.
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
// field value that caused the error) and is encoded using to the connection's character set
// (UTF-8 by default). Treat is as untrusted input.
std::cerr << "Error: " << err.what() << '\n'
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
return 1;
}
catch (const std::exception& err)
{
std::cerr << "Error: " << err.what() << std::endl;
return 1;
}
}
#else
int main(int, char**) { std::cout << "Sorry, your compiler does not support C++20 coroutines" << std::endl; }
#endif
//]
+1
View File
@@ -45,6 +45,7 @@
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/mysql_collations.hpp>
#include <boost/mysql/mysql_server_errc.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/pool_params.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/resultset.hpp>
+87 -1
View File
@@ -28,6 +28,7 @@
#include <boost/mysql/detail/connection_impl.hpp>
#include <boost/mysql/detail/engine.hpp>
#include <boost/mysql/detail/execution_concepts.hpp>
#include <boost/mysql/detail/pipeline_concepts.hpp>
#include <boost/mysql/detail/throw_on_error_loc.hpp>
#include <boost/asio/any_io_executor.hpp>
@@ -129,6 +130,12 @@ class any_connection
detail::any_address_view,
decltype(asio::consign(std::declval<CompletionToken>(), std::unique_ptr<char[]>()))>;
// Used by tests
any_connection(std::size_t initial_read_buffer_size, std::unique_ptr<detail::engine> eng)
: impl_(initial_read_buffer_size, std::move(eng))
{
}
public:
/**
* \brief Constructs a connection object from an executor and an optional set of parameters.
@@ -140,7 +147,7 @@ public:
* an \ref any_connection_params object to this constructor.
*/
any_connection(boost::asio::any_io_executor ex, any_connection_params params = {})
: impl_(params.initial_read_buffer_size, create_engine(std::move(ex), params.ssl_context))
: any_connection(params.initial_read_buffer_size, create_engine(std::move(ex), params.ssl_context))
{
}
@@ -1079,6 +1086,85 @@ public:
std::forward<CompletionToken>(token)
);
}
/**
* \brief Runs a set of pipelined requests.
* \details
* Runs the pipeline described by `req` and stores its response in `res`.
* \n
* Request stages are seen by the server as a series of unrelated requests.
* As a consequence, all stages are always run, even if previous stages fail.
*
* If all stages succeed, the operation completes successfully. Thus, there is no need to check
* the per-stage error code in `res` if this operation completed successfully.
* \n
* If any stage fails with a non-fatal error (as per \ref is_fatal_error), the result of the operation
* is the first encountered error. You can check which stages succeeded and which ones didn't by
* inspecting each stage in `res`.
* \n
* If any stage fails with a fatal error, the result of the operation is the fatal error.
* Successive stages will be marked as failed with the fatal error. The server may or may
* not have processed such stages.
*/
template <BOOST_MYSQL_PIPELINE_REQUEST_TYPE PipelineRequestType>
void run_pipeline(
const PipelineRequestType& req,
typename PipelineRequestType::response_type& res,
error_code& err,
diagnostics& diag
)
{
impl_.run(impl_.make_params_pipeline(req, res, diag), err);
}
/// \copydoc run_pipeline
template <BOOST_MYSQL_PIPELINE_REQUEST_TYPE PipelineRequestType>
void run_pipeline(const PipelineRequestType& req, typename PipelineRequestType::response_type& res)
{
error_code err;
diagnostics diag;
run_pipeline(req, res, err, diag);
detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION);
}
/**
* \copydoc run_pipeline
* \details
* \par Handler signature
* The handler signature for this operation is `void(boost::mysql::error_code)`.
*
* \par Object lifetimes
* The request and response objects must be kept alive and should not be modified
* until the operation completes.
*/
template <
BOOST_MYSQL_PIPELINE_REQUEST_TYPE PipelineRequestType,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken>
auto async_run_pipeline(
const PipelineRequestType& req,
typename PipelineRequestType::response_type& res,
CompletionToken&& token
) BOOST_MYSQL_RETURN_TYPE(detail::async_run_pipeline_t<CompletionToken&&>)
{
return async_run_pipeline(req, res, impl_.shared_diag(), std::forward<CompletionToken>(token));
}
/// \copydoc async_run_pipeline
template <
BOOST_MYSQL_PIPELINE_REQUEST_TYPE PipelineRequestType,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken>
auto async_run_pipeline(
const PipelineRequestType& req,
typename PipelineRequestType::response_type& res,
diagnostics& diag,
CompletionToken&& token
) BOOST_MYSQL_RETURN_TYPE(detail::async_run_pipeline_t<CompletionToken&&>)
{
return this->impl_.async_run(
impl_.make_params_pipeline(req, res, diag),
std::forward<CompletionToken>(token)
);
}
};
} // namespace mysql
+18 -7
View File
@@ -9,15 +9,14 @@
#define BOOST_MYSQL_DETAIL_ALGO_PARAMS_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/any_execution_request.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
@@ -25,8 +24,15 @@
namespace boost {
namespace mysql {
class rows_view;
class diagnostics;
class statement;
namespace detail {
class execution_processor;
class execution_state_impl;
struct connect_algo_params
{
diagnostics* diag;
@@ -114,7 +120,6 @@ struct ping_algo_params
struct reset_connection_algo_params
{
diagnostics* diag;
character_set charset; // set a non-empty character set to pipeline a SET NAMES with the reset request
using result_type = void;
};
@@ -141,8 +146,14 @@ struct close_connection_algo_params
using result_type = void;
};
template <class AlgoParams>
using has_void_result = std::is_same<typename AlgoParams::result_type, void>;
struct run_pipeline_algo_params
{
diagnostics* diag;
detail::pipeline_request_view request;
detail::pipeline_response_ref response;
using result_type = void;
};
} // namespace detail
} // namespace mysql
+19 -22
View File
@@ -26,6 +26,7 @@
#include <boost/mysql/detail/connect_params_helpers.hpp>
#include <boost/mysql/detail/engine.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/writable_field_traits.hpp>
#include <boost/core/ignore_unused.hpp>
@@ -135,6 +136,9 @@ make_request_getter(const bound_statement_tuple<WritableFieldTuple>& req, std::v
// helpers to run algos
//
template <class AlgoParams>
using has_void_result = std::is_same<typename AlgoParams::result_type, void>;
template <class AlgoParams, bool is_void>
struct completion_signature_impl;
@@ -560,34 +564,24 @@ public:
ping_algo_params make_params_ping(diagnostics& diag) const { return {&diag}; }
// Reset connection
reset_connection_algo_params make_params_reset_connection(
diagnostics& diag,
const character_set& charset = {}
) const
{
return {&diag, charset};
}
// Reset connection and issue a SET NAMES, using a pipeline.
// Used internally by connection_pool.
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
auto async_reset_with_charset(const character_set& charset, CompletionToken&& token)
BOOST_MYSQL_RETURN_TYPE(decltype(std::declval<connection_impl&>().async_run(
std::declval<reset_connection_algo_params>(),
std::forward<CompletionToken>(token)
)))
{
return async_run(
make_params_reset_connection(shared_diag(), charset),
std::forward<CompletionToken>(token)
);
}
reset_connection_algo_params make_params_reset_connection(diagnostics& diag) const { return {&diag}; }
// Quit connection
quit_connection_algo_params make_params_quit(diagnostics& diag) const { return {&diag}; }
// Close connection
close_connection_algo_params make_params_close(diagnostics& diag) const { return {&diag}; }
// Run pipeline
template <class PipelineRequestType>
static run_pipeline_algo_params make_params_pipeline(
const PipelineRequestType& req,
typename PipelineRequestType::response_type& response,
diagnostics& diag
)
{
return {&diag, access::get_impl(req).to_view(), pipeline_response_ref(response)};
}
};
// To use some completion tokens, like deferred, in C++11, the old macros
@@ -651,6 +645,9 @@ using async_quit_connection_t = async_run_t<quit_connection_algo_params, Complet
template <class CompletionToken>
using async_close_connection_t = async_run_t<close_connection_algo_params, CompletionToken>;
template <class CompletionToken>
using async_run_pipeline_t = async_run_t<run_pipeline_algo_params, CompletionToken>;
} // namespace detail
} // namespace mysql
} // namespace boost
+12 -12
View File
@@ -72,7 +72,7 @@ struct run_algo_op
self.complete(stored_ec_);
return;
}
else if (act.type() == next_action::type_t::read)
else if (act.type() == next_action_type::read)
{
BOOST_MYSQL_YIELD(
resume_point_,
@@ -85,7 +85,7 @@ struct run_algo_op
)
has_done_io_ = true;
}
else if (act.type() == next_action::type_t::write)
else if (act.type() == next_action_type::write)
{
BOOST_MYSQL_YIELD(
resume_point_,
@@ -98,24 +98,24 @@ struct run_algo_op
)
has_done_io_ = true;
}
else if (act.type() == next_action::type_t::ssl_handshake)
else if (act.type() == next_action_type::ssl_handshake)
{
BOOST_MYSQL_YIELD(resume_point_, 4, stream_.async_ssl_handshake(std::move(self)))
has_done_io_ = true;
}
else if (act.type() == next_action::type_t::ssl_shutdown)
else if (act.type() == next_action_type::ssl_shutdown)
{
BOOST_MYSQL_YIELD(resume_point_, 5, stream_.async_ssl_shutdown(std::move(self)))
has_done_io_ = true;
}
else if (act.type() == next_action::type_t::connect)
else if (act.type() == next_action_type::connect)
{
BOOST_MYSQL_YIELD(resume_point_, 6, stream_.async_connect(std::move(self)))
has_done_io_ = true;
}
else
{
BOOST_ASSERT(act.type() == next_action::type_t::close);
BOOST_ASSERT(act.type() == next_action_type::close);
stream_.close(io_ec);
}
}
@@ -180,7 +180,7 @@ public:
ec = act.error();
return;
}
else if (act.type() == next_action::type_t::read)
else if (act.type() == next_action_type::read)
{
bytes_transferred = stream_.read_some(
to_buffer(act.read_args().buffer),
@@ -188,7 +188,7 @@ public:
io_ec
);
}
else if (act.type() == next_action::type_t::write)
else if (act.type() == next_action_type::write)
{
bytes_transferred = stream_.write_some(
asio::buffer(act.write_args().buffer),
@@ -196,21 +196,21 @@ public:
io_ec
);
}
else if (act.type() == next_action::type_t::ssl_handshake)
else if (act.type() == next_action_type::ssl_handshake)
{
stream_.ssl_handshake(io_ec);
}
else if (act.type() == next_action::type_t::ssl_shutdown)
else if (act.type() == next_action_type::ssl_shutdown)
{
stream_.ssl_shutdown(io_ec);
}
else if (act.type() == next_action::type_t::connect)
else if (act.type() == next_action_type::connect)
{
stream_.connect(io_ec);
}
else
{
BOOST_ASSERT(act.type() == next_action::type_t::close);
BOOST_ASSERT(act.type() == next_action_type::close);
stream_.close(io_ec);
}
}
+33 -24
View File
@@ -19,20 +19,20 @@ namespace boost {
namespace mysql {
namespace detail {
enum class next_action_type
{
none,
write,
read,
ssl_handshake,
ssl_shutdown,
connect,
close,
};
class next_action
{
public:
enum class type_t
{
none,
write,
read,
ssl_handshake,
ssl_shutdown,
connect,
close,
};
struct read_args_t
{
span<std::uint8_t> buffer;
@@ -45,11 +45,11 @@ public:
bool use_ssl;
};
next_action(error_code ec = {}) noexcept : type_(type_t::none), data_(ec) {}
next_action(error_code ec = {}) noexcept : type_(next_action_type::none), data_(ec) {}
// Type
type_t type() const noexcept { return type_; }
bool is_done() const noexcept { return type_ == type_t::none; }
next_action_type type() const noexcept { return type_; }
bool is_done() const noexcept { return type_ == next_action_type::none; }
bool success() const noexcept { return is_done() && !data_.ec; }
// Arguments
@@ -60,24 +60,33 @@ public:
}
read_args_t read_args() const noexcept
{
BOOST_ASSERT(type_ == type_t::read);
BOOST_ASSERT(type_ == next_action_type::read);
return data_.read_args;
}
write_args_t write_args() const noexcept
{
BOOST_ASSERT(type_ == type_t::write);
BOOST_ASSERT(type_ == next_action_type::write);
return data_.write_args;
}
static next_action connect() noexcept { return next_action(type_t::connect, data_t()); }
static next_action read(read_args_t args) noexcept { return next_action(type_t::read, args); }
static next_action write(write_args_t args) noexcept { return next_action(type_t::write, args); }
static next_action ssl_handshake() noexcept { return next_action(type_t::ssl_handshake, data_t()); }
static next_action ssl_shutdown() noexcept { return next_action(type_t::ssl_shutdown, data_t()); }
static next_action close() noexcept { return next_action(type_t::close, data_t()); }
static next_action connect() noexcept { return next_action(next_action_type::connect, data_t()); }
static next_action read(read_args_t args) noexcept { return next_action(next_action_type::read, args); }
static next_action write(write_args_t args) noexcept
{
return next_action(next_action_type::write, args);
}
static next_action ssl_handshake() noexcept
{
return next_action(next_action_type::ssl_handshake, data_t());
}
static next_action ssl_shutdown() noexcept
{
return next_action(next_action_type::ssl_shutdown, data_t());
}
static next_action close() noexcept { return next_action(next_action_type::close, data_t()); }
private:
type_t type_{type_t::none};
next_action_type type_{next_action_type::none};
union data_t
{
error_code ec;
@@ -90,7 +99,7 @@ private:
data_t(write_args_t args) noexcept : write_args(args) {}
} data_;
next_action(type_t t, data_t data) noexcept : type_(t), data_(data) {}
next_action(next_action_type t, data_t data) noexcept : type_(t), data_(data) {}
};
} // namespace detail
+165
View File
@@ -0,0 +1,165 @@
//
// 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_DETAIL_PIPELINE_HPP
#define BOOST_MYSQL_DETAIL_PIPELINE_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
namespace boost {
namespace mysql {
namespace detail {
class execution_processor;
enum class pipeline_stage_kind
{
execute,
prepare_statement,
close_statement,
reset_connection,
set_character_set,
ping,
};
struct pipeline_request_stage
{
pipeline_stage_kind kind;
std::uint8_t seqnum;
union stage_specific_t
{
std::nullptr_t nothing;
resultset_encoding enc;
character_set charset;
stage_specific_t() noexcept : nothing() {}
stage_specific_t(resultset_encoding v) noexcept : enc(v) {}
stage_specific_t(character_set v) noexcept : charset(v) {}
} stage_specific;
};
struct pipeline_request_view
{
span<const std::uint8_t> buffer;
span<const pipeline_request_stage> stages;
};
template <class T>
struct pipeline_response_traits;
class pipeline_response_ref
{
// We multiplex calls to store a single function pointer, instead of four
enum fn_type
{
fn_type_setup,
fn_type_get_processor,
fn_type_set_result,
fn_type_set_error
};
union fn_args
{
struct setup_t
{
fn_type type;
span<const pipeline_request_stage> request_stages;
static_assert(std::is_standard_layout<decltype(request_stages)>::value, "Internal error");
} setup;
struct get_processor_t
{
fn_type type;
std::size_t index;
} get_processor;
struct set_result_t
{
fn_type type;
std::size_t index;
statement stmt;
} set_result;
struct set_error_t
{
fn_type type;
std::size_t index;
const error_code* ec;
diagnostics* diag;
} set_error;
fn_args(span<const pipeline_request_stage> v) noexcept : setup{fn_type_setup, v} {}
fn_args(std::size_t index) noexcept : get_processor{fn_type_get_processor, index} {}
fn_args(std::size_t index, statement stmt) noexcept : set_result{fn_type_set_result, index, stmt} {}
fn_args(std::size_t index, const error_code* ec, diagnostics* diag) noexcept
: set_error{fn_type_set_error, index, ec, diag}
{
}
};
static execution_processor* null_invoke(void*, fn_args) { return nullptr; }
template <class T>
static execution_processor* do_invoke(void* obj, fn_args args)
{
using traits_t = pipeline_response_traits<T>;
auto& self = *static_cast<T*>(obj);
switch (args.setup.type)
{
case fn_type_setup: traits_t::setup(self, args.setup.request_stages); return nullptr;
case fn_type_get_processor: return &traits_t::get_processor(self, args.get_processor.index);
case fn_type_set_result:
traits_t::set_result(self, args.set_result.index, args.set_result.stmt);
return nullptr;
case fn_type_set_error:
traits_t::set_error(
self,
args.set_error.index,
*args.set_error.ec,
std::move(*args.set_error.diag)
);
return nullptr;
default: BOOST_ASSERT(false); return nullptr;
}
}
void* obj_;
execution_processor* (*fn_)(void*, fn_args);
public:
pipeline_response_ref() : obj_(nullptr), fn_(&null_invoke) {}
template <class T, class = typename std::enable_if<!std::is_same<T, pipeline_response_ref>::value>::type>
pipeline_response_ref(T& obj) : obj_(&obj), fn_(&do_invoke<T>)
{
}
void setup(span<const pipeline_request_stage> request_stages) { fn_(obj_, {request_stages}); }
execution_processor& get_processor(std::size_t stage_idx) { return *fn_(obj_, {stage_idx}); }
void set_result(std::size_t stage_idx, statement result) { fn_(obj_, {stage_idx, result}); }
void set_error(std::size_t stage_idx, error_code ec, diagnostics&& diag)
{
fn_(obj_, {stage_idx, &ec, &diag});
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,56 @@
//
// 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_DETAIL_PIPELINE_CONCEPTS_HPP
#define BOOST_MYSQL_DETAIL_PIPELINE_CONCEPTS_HPP
#include <boost/mysql/detail/config.hpp>
#include <type_traits>
namespace boost {
namespace mysql {
namespace detail {
// PipelineStageType
template <class T>
struct is_pipeline_stage_type : std::false_type
{
};
// PipelineRequestType
template <class T>
struct is_pipeline_request_type : std::false_type
{
};
#ifdef BOOST_MYSQL_HAS_CONCEPTS
// If you're getting errors pointing to this line, you're passing invalid stage types
// to a pipeline. Valid types include execute_stage, prepare_statement_stage, close_statement_stage,
// reset_connection_stage and set_character_set_stage
template <class T>
concept pipeline_stage_type = is_pipeline_stage_type<T>::value;
template <class T>
concept pipeline_request_type = is_pipeline_request_type<T>::value;
#define BOOST_MYSQL_PIPELINE_STAGE_TYPE ::boost::mysql::detail::pipeline_stage_type
#define BOOST_MYSQL_PIPELINE_REQUEST_TYPE ::boost::mysql::detail::pipeline_request_type
#else
#define BOOST_MYSQL_PIPELINE_STAGE_TYPE class
#define BOOST_MYSQL_PIPELINE_REQUEST_TYPE class
#endif
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -18,7 +18,7 @@ namespace boost {
namespace mysql {
namespace detail {
inline void throw_on_error_loc(error_code err, const diagnostics& diag, const boost::source_location& loc)
inline void throw_on_error_loc(error_code err, const diagnostics& diag, const source_location& loc)
{
if (err)
{
@@ -72,6 +72,19 @@ struct is_writable_field : std::integral_constant<bool, writable_field_traits<T>
{
};
#ifdef BOOST_MYSQL_HAS_CONCEPTS
template <class T>
concept writable_field = is_writable_field<T>::value;
#define BOOST_MYSQL_WRITABLE_FIELD ::boost::mysql::detail::writable_field
#else
#define BOOST_MYSQL_WRITABLE_FIELD class
#endif
// field_view_forward_iterator
template <typename T, typename = void>
struct is_field_view_forward_iterator : std::false_type
+38 -5
View File
@@ -11,6 +11,8 @@
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/system/system_error.hpp>
namespace boost {
@@ -22,20 +24,20 @@ namespace mysql {
* Like `boost::system::system_error`, but adds a \ref diagnostics member
* containing additional information.
*/
class error_with_diagnostics : public boost::system::system_error
class error_with_diagnostics : public system::system_error
{
diagnostics diag_;
static boost::system::system_error create_base(const error_code& err, const diagnostics& diag)
static system::system_error create_base(const error_code& err, const diagnostics& diag)
{
return diag.client_message().empty() ? boost::system::system_error(err)
: boost::system::system_error(err, diag.client_message());
return diag.client_message().empty() ? system::system_error(err)
: system::system_error(err, diag.client_message());
}
public:
/// Initializing constructor.
error_with_diagnostics(const error_code& err, const diagnostics& diag)
: boost::system::system_error(create_base(err, diag)), diag_(diag)
: system::system_error(create_base(err, diag)), diag_(diag)
{
}
@@ -50,7 +52,38 @@ public:
const diagnostics& get_diagnostics() const noexcept { return diag_; }
};
/**
* \brief A custom error type to be used with system::result.
* \details
* A custom error type containing an error code and a diagnostics object.
* It can be used as the second template parameter of `boost::system::result`
* (`boost::system::result<T, errcode_with_diagnostics>`).
* \n
* When `system::result::value()` throws, the exception type is \ref error_with_diagnostics,
* containing the error code and diagnostics contained in this type.
*/
struct errcode_with_diagnostics
{
/// The error code.
error_code code;
/// The diagnostics object.
diagnostics diag;
};
// Required to make errcode_and_diagnostics compatible with boost::system
#ifndef BOOST_MYSQL_DOXYGEN
[[noreturn]] BOOST_MYSQL_DECL void throw_exception_from_error(
const errcode_with_diagnostics& e,
const source_location& loc
);
#endif
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/error_with_diagnostics.ipp>
#endif
#endif
@@ -98,6 +98,7 @@ BOOST_MYSQL_INSTANTIATE_SETUP(ping_algo_params)
BOOST_MYSQL_INSTANTIATE_SETUP(reset_connection_algo_params)
BOOST_MYSQL_INSTANTIATE_SETUP(quit_connection_algo_params)
BOOST_MYSQL_INSTANTIATE_SETUP(close_connection_algo_params)
BOOST_MYSQL_INSTANTIATE_SETUP(run_pipeline_algo_params)
BOOST_MYSQL_INSTANTIATE_GET_RESULT(read_some_rows_algo_params)
BOOST_MYSQL_INSTANTIATE_GET_RESULT(read_some_rows_dynamic_algo_params)
@@ -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_IMPL_ERROR_WITH_DIAGNOSTICS_IPP
#define BOOST_MYSQL_IMPL_ERROR_WITH_DIAGNOSTICS_IPP
#pragma once
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/throw_exception.hpp>
void boost::mysql::throw_exception_from_error(const errcode_with_diagnostics& e, const source_location& loc)
{
::boost::throw_exception(error_with_diagnostics(e.code, e.diag), loc);
}
#endif
@@ -13,6 +13,7 @@
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/detail/connection_pool_fwd.hpp>
@@ -57,6 +58,10 @@ struct conn_shared_state
diagnostics last_diag;
};
// Helper to create the reset pipeline (reset + set names utf8mb4)
using reset_pipeline_req_t = static_pipeline_request<reset_connection_stage, set_character_set_stage>;
inline reset_pipeline_req_t make_reset_pipeline() { return reset_pipeline_req_t({}, {utf8mb4_charset}); }
// The templated type is never exposed to the user. We template
// so tests can inject mocks.
template <class IoTraits>
@@ -75,6 +80,8 @@ class basic_connection_node : public intrusive::list_base_hook<>,
diagnostics connect_diag_;
timer_type collection_timer_; // Notifications about collections. A separate timer makes potential race
// conditions not harmful
const reset_pipeline_req_t* reset_pipeline_req_;
reset_pipeline_req_t::response_type reset_pipeline_res_;
// Thread-safe
std::atomic<collection_state> collection_state_{collection_state::none};
@@ -147,7 +154,11 @@ class basic_connection_node : public intrusive::list_base_hook<>,
break;
case next_connection_action::reset:
run_with_timeout(
access::get_impl(node_.conn_).async_reset_with_charset(utf8mb4_charset, asio::deferred),
node_.conn_.async_run_pipeline(
*node_.reset_pipeline_req_,
node_.reset_pipeline_res_,
asio::deferred
),
node_.timer_,
node_.params_->ping_timeout,
std::move(self)
@@ -172,13 +183,15 @@ public:
internal_pool_params& params,
boost::asio::any_io_executor ex,
boost::asio::any_io_executor conn_ex,
conn_shared_state<IoTraits>& shared_st
conn_shared_state<IoTraits>& shared_st,
const reset_pipeline_req_t* reset_pipeline_req
)
: params_(&params),
shared_st_(&shared_st),
conn_(std::move(conn_ex), params.make_ctor_params()),
timer_(ex),
collection_timer_(ex, (std::chrono::steady_clock::time_point::max)())
collection_timer_(ex, (std::chrono::steady_clock::time_point::max)()),
reset_pipeline_req_(reset_pipeline_req)
{
}
@@ -191,8 +204,8 @@ public:
// This initiation must be invoked within the pool's executor
template <class CompletionToken>
auto async_run(CompletionToken&& token)
-> decltype(asio::async_compose<CompletionToken, void(error_code)>(connection_task_op{*this}, token))
auto async_run(CompletionToken&& token
) -> decltype(asio::async_compose<CompletionToken, void(error_code)>(connection_task_op{*this}, token))
{
return asio::async_compose<CompletionToken, void(error_code)>(connection_task_op{*this}, token);
}
@@ -67,6 +67,7 @@ class basic_pool_impl : public std::enable_shared_from_this<basic_pool_impl<IoTr
shared_state_type shared_st_;
wait_group wait_gp_;
timer_type cancel_timer_;
const reset_pipeline_req_t reset_pipeline_req_{make_reset_pipeline()};
std::shared_ptr<this_type> shared_from_this_wrapper()
{
@@ -76,7 +77,7 @@ class basic_pool_impl : public std::enable_shared_from_this<basic_pool_impl<IoTr
void create_connection()
{
all_conns_.emplace_back(params_, ex_, conn_ex_, shared_st_);
all_conns_.emplace_back(params_, ex_, conn_ex_, shared_st_, &reset_pipeline_req_);
wait_gp_.run_task(all_conns_.back().async_run(asio::deferred));
}
@@ -323,6 +324,7 @@ public:
shared_state_type& shared_state() noexcept { return shared_st_; }
internal_pool_params& params() noexcept { return params_; }
asio::any_io_executor connection_ex() noexcept { return conn_ex_; }
const reset_pipeline_req_t& reset_pipeline_request() const { return reset_pipeline_req_; }
};
} // namespace detail
@@ -41,7 +41,6 @@
#include <cstddef>
#include <cstdint>
#include <type_traits>
namespace boost {
namespace mysql {
@@ -106,8 +105,6 @@ BOOST_ATTRIBUTE_NODISCARD inline error_code deserialize_prepare_stmt_response(
// Execution messages
struct execute_response
{
static_assert(std::is_trivially_destructible<error_code>::value, "");
enum class type_t
{
num_fields,
@@ -126,7 +126,7 @@ template <class Serializable>
inline std::uint8_t serialize_top_level(
const Serializable& input,
std::vector<std::uint8_t>& to,
std::uint8_t seqnum,
std::uint8_t seqnum = 0,
std::size_t frame_size = max_packet_size
)
{
@@ -12,26 +12,23 @@
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/quit_connection.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
namespace detail {
class close_connection_algo : public sansio_algorithm
class close_connection_algo
{
int resume_point_{0};
quit_connection_algo quit_;
error_code stored_ec_;
public:
close_connection_algo(connection_state_data& st, close_connection_algo_params params) noexcept
: sansio_algorithm(st), quit_(st, {params.diag})
{
}
close_connection_algo(close_connection_algo_params params) noexcept : quit_({params.diag}) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
@@ -43,11 +40,11 @@ public:
quit_.diag().clear();
// If we're not connected, we're done
if (!st_->is_connected)
if (!st.is_connected)
return next_action();
// Attempt quit
while (!(act = quit_.resume(ec)).is_done())
while (!(act = quit_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 1, act)
stored_ec_ = act.error();
@@ -8,74 +8,36 @@
#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CLOSE_STATEMENT_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CLOSE_STATEMENT_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
namespace detail {
class close_statement_algo : public sansio_algorithm
inline run_pipeline_algo_params setup_close_statement_pipeline(
connection_state_data& st,
close_statement_algo_params params
)
{
int resume_point_{0};
diagnostics* diag_;
std::uint32_t stmt_id_;
std::uint8_t close_seqnum_{0};
std::uint8_t ping_seqnum_{0};
public:
close_statement_algo(connection_state_data& st, close_statement_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag), stmt_id_(params.stmt_id)
{
}
next_action resume(error_code ec)
{
if (ec)
return ec;
switch (resume_point_)
{
case 0:
// Clear diagnostics
diag_->clear();
// Compose the requests. We pipeline a ping with the close statement
// to force the server send a response. Otherwise, the client ends up waiting
// for the next TCP ACK, which takes some milliseconds to be sent
// (see https://github.com/boostorg/mysql/issues/181)
st_->writer.prepare_pipelined_write(
close_stmt_command{stmt_id_},
close_seqnum_,
ping_command{},
ping_seqnum_
);
BOOST_MYSQL_YIELD(resume_point_, 1, next_action::write({}))
// Read ping response
BOOST_MYSQL_YIELD(resume_point_, 2, read(ping_seqnum_))
// Process the OK packet
return st_->deserialize_ok(*diag_);
}
return next_action();
}
};
st.write_buffer.clear();
auto seqnum1 = serialize_top_level(close_stmt_command{params.stmt_id}, st.write_buffer);
auto seqnum2 = serialize_top_level(ping_command{}, st.write_buffer);
st.shared_pipeline_stages = {
{{pipeline_stage_kind::close_statement, seqnum1, {}}, {pipeline_stage_kind::ping, seqnum2, {}}}
};
return {
params.diag,
pipeline_request_view{st.write_buffer, st.shared_pipeline_stages},
pipeline_response_ref{}
};
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP_ */
#endif
@@ -17,25 +17,24 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/handshake.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
namespace detail {
class connect_algo : public sansio_algorithm
class connect_algo
{
int resume_point_{0};
handshake_algo handshake_;
error_code stored_ec_;
public:
connect_algo(connection_state_data& st, connect_algo_params params) noexcept
: sansio_algorithm(st), handshake_(st, {params.diag, params.hparams, params.secure_channel})
connect_algo(connect_algo_params params) noexcept
: handshake_({params.diag, params.hparams, params.secure_channel})
{
}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
@@ -52,7 +51,7 @@ public:
return ec;
// Handshake
while (!(act = handshake_.resume(ec)).is_done())
while (!(act = handshake_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
// If handshake failed, close the stream ignoring the result
@@ -29,11 +29,11 @@
#include <boost/mysql/impl/internal/sansio/read_some_rows.hpp>
#include <boost/mysql/impl/internal/sansio/read_some_rows_dynamic.hpp>
#include <boost/mysql/impl/internal/sansio/reset_connection.hpp>
#include <boost/mysql/impl/internal/sansio/run_pipeline.hpp>
#include <boost/mysql/impl/internal/sansio/set_character_set.hpp>
#include <boost/mysql/impl/internal/sansio/start_execution.hpp>
#include <boost/mysql/impl/internal/sansio/top_level_algo.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/variant2/variant.hpp>
#include <cstddef>
@@ -52,12 +52,10 @@ template <> struct get_algo<read_resultset_head_algo_params> { using type = read
template <> struct get_algo<read_some_rows_algo_params> { using type = read_some_rows_algo; };
template <> struct get_algo<read_some_rows_dynamic_algo_params> { using type = read_some_rows_dynamic_algo; };
template <> struct get_algo<prepare_statement_algo_params> { using type = prepare_statement_algo; };
template <> struct get_algo<close_statement_algo_params> { using type = close_statement_algo; };
template <> struct get_algo<set_character_set_algo_params> { using type = set_character_set_algo; };
template <> struct get_algo<ping_algo_params> { using type = ping_algo; };
template <> struct get_algo<reset_connection_algo_params> { using type = reset_connection_algo; };
template <> struct get_algo<quit_connection_algo_params> { using type = quit_connection_algo; };
template <> struct get_algo<close_connection_algo_params> { using type = close_connection_algo; };
template <> struct get_algo<run_pipeline_algo_params> { using type = run_pipeline_algo; };
template <class AlgoParams> using get_algo_t = typename get_algo<AlgoParams>::type;
// clang-format on
@@ -76,12 +74,10 @@ class connection_state
read_some_rows_algo,
read_some_rows_dynamic_algo,
prepare_statement_algo,
close_statement_algo,
set_character_set_algo,
ping_algo,
reset_connection_algo,
quit_connection_algo,
close_connection_algo>;
close_connection_algo,
run_pipeline_algo>;
connection_state_data st_data_;
any_algo algo_;
@@ -92,12 +88,15 @@ public:
// the need for a special null algo
connection_state(std::size_t read_buffer_size, bool transport_supports_ssl)
: st_data_(read_buffer_size, transport_supports_ssl),
algo_(top_level_algo<ping_algo>(st_data_, ping_algo_params{&st_data_.shared_diag}))
algo_(top_level_algo<quit_connection_algo>(
st_data_,
quit_connection_algo_params{&st_data_.shared_diag}
))
{
}
const connection_state_data& data() const noexcept { return st_data_; }
connection_state_data& data() noexcept { return st_data_; }
const connection_state_data& data() const { return st_data_; }
connection_state_data& data() { return st_data_; }
template <class AlgoParams>
any_resumable_ref setup(AlgoParams params)
@@ -105,11 +104,22 @@ public:
return any_resumable_ref(algo_.emplace<top_level_algo<get_algo_t<AlgoParams>>>(st_data_, params));
}
any_resumable_ref setup(close_statement_algo_params params)
{
return setup(setup_close_statement_pipeline(st_data_, params));
}
any_resumable_ref setup(reset_connection_algo_params params)
{
return setup(setup_reset_connection_pipeline(st_data_, params));
}
any_resumable_ref setup(ping_algo_params params) { return setup(setup_ping_pipeline(st_data_, params)); }
template <typename AlgoParams>
typename AlgoParams::result_type result() const
{
static_assert(!has_void_result<AlgoParams>(), "Internal error");
return variant2::get<top_level_algo<get_algo_t<AlgoParams>>>(algo_).inner_algo().result();
return variant2::get<top_level_algo<get_algo_t<AlgoParams>>>(algo_).inner_algo().result(st_data_);
}
};
@@ -13,12 +13,17 @@
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/db_flavor.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/message_reader.hpp>
#include <boost/mysql/impl/internal/sansio/message_writer.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace boost {
@@ -50,6 +55,9 @@ struct connection_state_data
// Temporary field storage, re-used by several ops
std::vector<field_view> shared_fields;
// Temporary pipeline stage storage, re-used by several ops
std::array<pipeline_request_stage, 2> shared_pipeline_stages;
// Do we want to retain metadata strings or not? Used to save allocations
metadata_mode meta_mode{metadata_mode::minimal};
@@ -63,14 +71,16 @@ struct connection_state_data
// The current character set, or a default-constructed character set (will all nullptrs) if unknown
character_set current_charset{};
// Reader and writer
// The write buffer
std::vector<std::uint8_t> write_buffer;
// Reader
message_reader reader;
message_writer writer;
bool ssl_active() const noexcept { return ssl == ssl_state::active; }
bool supports_ssl() const noexcept { return ssl != ssl_state::unsupported; }
bool ssl_active() const { return ssl == ssl_state::active; }
bool supports_ssl() const { return ssl != ssl_state::unsupported; }
const character_set* charset_ptr() const noexcept
const character_set* charset_ptr() const
{
return current_charset.name.empty() ? nullptr : &current_charset;
}
@@ -95,10 +105,27 @@ struct connection_state_data
}
// Reads an OK packet from the reader. This operation is repeated in several places.
error_code deserialize_ok(diagnostics& diag) noexcept
error_code deserialize_ok(diagnostics& diag)
{
return deserialize_ok_response(reader.message(), flavor, diag, backslash_escapes);
}
// Helpers for sans-io algorithms
next_action read(std::uint8_t& seqnum, bool keep_parsing_state = false)
{
// buffer is attached by top_level_algo
reader.prepare_read(seqnum, keep_parsing_state);
return next_action::read({});
}
template <class Serializable>
next_action write(const Serializable& msg, std::uint8_t& seqnum)
{
// use_ssl is attached by top_level_algo
write_buffer.clear();
seqnum = serialize_top_level(msg, write_buffer, seqnum);
return next_action::write({write_buffer, false});
}
};
} // namespace detail
@@ -19,33 +19,78 @@
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/read_resultset_head.hpp>
#include <boost/mysql/impl/internal/sansio/read_some_rows.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/mysql/impl/internal/sansio/start_execution.hpp>
namespace boost {
namespace mysql {
namespace detail {
class execute_algo : public sansio_algorithm
class read_execute_response_algo
{
int resume_point_{0};
start_execution_algo start_execution_st_;
read_resultset_head_algo read_head_st_;
read_some_rows_algo read_some_rows_st_;
diagnostics& diag() noexcept { return *read_head_st_.params().diag; }
execution_processor& processor() noexcept { return *read_head_st_.params().proc; }
public:
execute_algo(connection_state_data& st, execute_algo_params params) noexcept
: sansio_algorithm(st),
start_execution_st_(st, start_execution_algo_params{params.diag, params.req, params.proc}),
read_head_st_(st, read_resultset_head_algo_params{params.diag, params.proc}),
read_some_rows_st_(st, read_some_rows_algo_params{params.diag, params.proc, output_ref()})
read_execute_response_algo(diagnostics* diag, execution_processor* proc) noexcept
: read_head_st_(read_resultset_head_algo_params{diag, proc}),
read_some_rows_st_(read_some_rows_algo_params{diag, proc, output_ref()})
{
}
next_action resume(error_code ec)
diagnostics& diag() { return read_head_st_.diag(); }
execution_processor& processor() { return read_head_st_.processor(); }
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
switch (resume_point_)
{
case 0:
while (!processor().is_complete())
{
if (processor().is_reading_head())
{
read_head_st_.reset();
while (!(act = read_head_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 1, act)
if (act.error())
return act;
}
else if (processor().is_reading_rows())
{
read_some_rows_st_.reset();
while (!(act = read_some_rows_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
if (act.error())
return act;
}
}
}
return next_action();
}
};
class execute_algo
{
int resume_point_{0};
start_execution_algo start_execution_st_;
read_execute_response_algo read_response_st_;
diagnostics& diag() { return read_response_st_.diag(); }
execution_processor& processor() { return read_response_st_.processor(); }
public:
execute_algo(execute_algo_params params) noexcept
: start_execution_st_(start_execution_algo_params{params.diag, params.req, params.proc}),
read_response_st_(params.diag, params.proc)
{
}
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
@@ -54,31 +99,15 @@ public:
case 0:
// Send request and read the first response
while (!(act = start_execution_st_.resume(ec)).is_done())
while (!(act = start_execution_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 1, act)
if (act.error())
return act;
// Read anything else
while (!processor().is_complete())
{
if (processor().is_reading_head())
{
read_head_st_ = read_resultset_head_algo(*st_, read_head_st_.params());
while (!(act = read_head_st_.resume(ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
if (act.error())
return act;
}
else if (processor().is_reading_rows())
{
read_some_rows_st_ = read_some_rows_algo(*st_, read_some_rows_st_.params());
while (!(act = read_some_rows_st_.resume(ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 3, act)
if (act.error())
return act;
}
}
while (!(act = read_response_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
return act;
}
return next_action();
@@ -25,7 +25,6 @@
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <cstdint>
@@ -41,10 +40,11 @@ inline capabilities conditional_capability(bool condition, std::uint32_t cap)
inline error_code process_capabilities(
const handshake_params& params,
const server_hello& hello,
capabilities& negotiated_caps
capabilities& negotiated_caps,
bool transport_supports_ssl
)
{
auto ssl = params.ssl();
auto ssl = transport_supports_ssl ? params.ssl() : ssl_mode::disable;
capabilities server_caps = hello.server_capabilities;
capabilities required_caps = mandatory_capabilities |
conditional_capability(!params.database().empty(), CLIENT_CONNECT_WITH_DB) |
@@ -65,7 +65,7 @@ inline error_code process_capabilities(
return error_code();
}
class handshake_algo : public sansio_algorithm
class handshake_algo
{
int resume_point_{0};
diagnostics* diag_;
@@ -77,7 +77,7 @@ class handshake_algo : public sansio_algorithm
// Attempts to map the collection_id to a character set. We try to be conservative
// here, since servers will happily accept unknown collation IDs, silently defaulting
// to the server's default character set (often latin1, which is not Unicode).
static character_set collation_id_to_charset(std::uint16_t collation_id) noexcept
static character_set collation_id_to_charset(std::uint16_t collation_id)
{
switch (collation_id)
{
@@ -90,9 +90,9 @@ class handshake_algo : public sansio_algorithm
}
// Once the handshake is processed, the capabilities are stored in the connection state
bool use_ssl() const noexcept { return st_->current_capabilities.has(CLIENT_SSL); }
bool use_ssl(const connection_state_data& st) const { return st.current_capabilities.has(CLIENT_SSL); }
error_code process_handshake(span<const std::uint8_t> buffer)
error_code process_handshake(connection_state_data& st, span<const std::uint8_t> buffer)
{
// Deserialize server hello
server_hello hello{};
@@ -102,16 +102,16 @@ class handshake_algo : public sansio_algorithm
// Check capabilities
capabilities negotiated_caps;
err = process_capabilities(hparams_, hello, negotiated_caps);
err = process_capabilities(hparams_, hello, negotiated_caps, st.supports_ssl());
if (err)
return err;
// Set capabilities & db flavor
st_->current_capabilities = negotiated_caps;
st_->flavor = hello.server;
st.current_capabilities = negotiated_caps;
st.flavor = hello.server;
// If we're using SSL, mark the channel as secure
secure_channel_ = secure_channel_ || use_ssl();
secure_channel_ = secure_channel_ || use_ssl(st);
// Compute auth response
return compute_auth_response(
@@ -124,19 +124,19 @@ class handshake_algo : public sansio_algorithm
}
// Response to that initial greeting
ssl_request compose_ssl_request()
ssl_request compose_ssl_request(const connection_state_data& st)
{
return ssl_request{
st_->current_capabilities,
st.current_capabilities,
static_cast<std::uint32_t>(max_packet_size),
hparams_.connection_collation(),
};
}
login_request compose_login_request()
login_request compose_login_request(const connection_state_data& st)
{
return login_request{
st_->current_capabilities,
st.current_capabilities,
static_cast<std::uint32_t>(max_packet_size),
hparams_.connection_collation(),
hparams_.username(),
@@ -170,48 +170,37 @@ class handshake_algo : public sansio_algorithm
}
// Composes an auth_switch_response message with the contents of auth_resp_
auth_switch_response compose_auth_switch_response() const noexcept
auth_switch_response compose_auth_switch_response() const
{
return auth_switch_response{auth_resp_.data};
}
void on_success(const ok_view& ok)
void on_success(connection_state_data& st, const ok_view& ok)
{
st_->is_connected = true;
st_->backslash_escapes = ok.backslash_escapes();
st_->current_charset = collation_id_to_charset(hparams_.connection_collation());
st.is_connected = true;
st.backslash_escapes = ok.backslash_escapes();
st.current_charset = collation_id_to_charset(hparams_.connection_collation());
}
error_code process_ok()
error_code process_ok(connection_state_data& st)
{
ok_view res{};
auto ec = deserialize_ok_packet(st_->reader.message(), res);
auto ec = deserialize_ok_packet(st.reader.message(), res);
if (ec)
return ec;
on_success(res);
on_success(st, res);
return error_code();
}
static handshake_params fix_ssl_mode(const handshake_params& input, bool transport_supports_ssl) noexcept
{
handshake_params res(input);
if (!transport_supports_ssl)
res.set_ssl(ssl_mode::disable);
return res;
}
public:
handshake_algo(connection_state_data& st, handshake_algo_params params) noexcept
: sansio_algorithm(st),
diag_(params.diag),
hparams_(fix_ssl_mode(params.hparams, st.supports_ssl())),
secure_channel_(params.secure_channel)
handshake_algo(handshake_algo_params params) noexcept
: diag_(params.diag), hparams_(params.hparams), secure_channel_(params.secure_channel)
{
}
diagnostics& diag() noexcept { return *diag_; }
diagnostics& diag() { return *diag_; }
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
@@ -224,44 +213,44 @@ public:
// Setup
diag_->clear();
st_->reset();
st.reset();
// Read server greeting
BOOST_MYSQL_YIELD(resume_point_, 1, read(sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 1, st.read(sequence_number_))
// Process server greeting
ec = process_handshake(st_->reader.message());
ec = process_handshake(st, st.reader.message());
if (ec)
return ec;
// SSL
if (use_ssl())
if (use_ssl(st))
{
// Send SSL request
BOOST_MYSQL_YIELD(resume_point_, 2, write(compose_ssl_request(), sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 2, st.write(compose_ssl_request(st), sequence_number_))
// SSL handshake
BOOST_MYSQL_YIELD(resume_point_, 3, next_action::ssl_handshake())
// Mark the connection as using ssl
st_->ssl = ssl_state::active;
st.ssl = ssl_state::active;
}
// Compose and send handshake response
BOOST_MYSQL_YIELD(resume_point_, 4, write(compose_login_request(), sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 4, st.write(compose_login_request(st), sequence_number_))
// Auth message exchange
while (true)
{
// Receive response
BOOST_MYSQL_YIELD(resume_point_, 5, read(sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 5, st.read(sequence_number_))
// Process it
resp = deserialize_handshake_server_response(st_->reader.message(), st_->flavor, *diag_);
resp = deserialize_handshake_server_response(st.reader.message(), st.flavor, *diag_);
if (resp.type == handhake_server_response::type_t::ok)
{
// Auth success, quit
on_success(resp.data.ok);
on_success(st, resp.data.ok);
return next_action();
}
else if (resp.type == handhake_server_response::type_t::error)
@@ -279,17 +268,17 @@ public:
BOOST_MYSQL_YIELD(
resume_point_,
6,
write(compose_auth_switch_response(), sequence_number_)
st.write(compose_auth_switch_response(), sequence_number_)
)
}
else if (resp.type == handhake_server_response::type_t::ok_follows)
{
// The next packet must be an OK packet. Read it
BOOST_MYSQL_YIELD(resume_point_, 7, read(sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 7, st.read(sequence_number_))
// Process it
// Regardless of whether we succeeded or not, we're done
return process_ok();
return process_ok(st);
}
else
{
@@ -304,7 +293,7 @@ public:
BOOST_MYSQL_YIELD(
resume_point_,
8,
write(compose_auth_switch_response(), sequence_number_)
st.write(compose_auth_switch_response(), sequence_number_)
)
}
}
@@ -41,7 +41,7 @@ public:
{
}
void reset() noexcept
void reset()
{
buffer_.reset();
state_ = parse_state();
@@ -50,7 +50,7 @@ public:
// Prepares a read operation. sequence_number should be kept alive until
// the next read is prepared or no more calls to resume() are expected.
// If keep_state=true, and the op is not complete, parsing state is preserved
void prepare_read(std::uint8_t& sequence_number, bool keep_state = false) noexcept
void prepare_read(std::uint8_t& sequence_number, bool keep_state = false)
{
if (!keep_state || done())
state_ = parse_state(sequence_number);
@@ -63,7 +63,7 @@ public:
bool done() const { return state_.resume_point == -1; }
// Returns any errors generated during parsing. Requires this->done()
error_code error() const noexcept
error_code error() const
{
BOOST_ASSERT(done());
return state_.ec;
@@ -71,7 +71,7 @@ public:
// Returns the last parsed message. Valid until prepare_buffer()
// is next called. Requires done() && !error()
span<const std::uint8_t> message() const noexcept
span<const std::uint8_t> message() const
{
BOOST_ASSERT(done());
BOOST_ASSERT(!error());
@@ -79,7 +79,7 @@ public:
}
// Returns buffer space suitable to read bytes to
span<std::uint8_t> buffer() noexcept { return buffer_.free_area(); }
span<std::uint8_t> buffer() { return buffer_.free_area(); }
// Removes old messages stored in the buffer, and resizes it, if required, to accomodate
// the message currently being parsed.
@@ -164,7 +164,7 @@ public:
}
// Exposed for testing
const read_buffer& internal_buffer() const noexcept { return buffer_; }
const read_buffer& internal_buffer() const { return buffer_; }
private:
read_buffer buffer_;
@@ -184,7 +184,7 @@ private:
parse_state(std::uint8_t& seqnum) noexcept : sequence_number(&seqnum) {}
} state_;
void set_required_size(std::size_t required_bytes) noexcept
void set_required_size(std::size_t required_bytes)
{
if (required_bytes > buffer_.pending_size())
state_.required_size = required_bytes - buffer_.pending_size();
@@ -1,83 +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_IMPL_INTERNAL_SANSIO_MESSAGE_WRITER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_MESSAGE_WRITER_HPP
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace boost {
namespace mysql {
namespace detail {
class message_writer
{
std::vector<std::uint8_t> buffer_;
std::size_t offset_{0};
void reset()
{
buffer_.clear();
offset_ = 0;
}
public:
message_writer() = default;
template <class Serializable>
void prepare_write(const Serializable& message, std::uint8_t& sequence_number)
{
reset();
sequence_number = serialize_top_level(message, buffer_, sequence_number);
resume(0);
}
// Serializes two messages into the write buffer. They must fit in a single frame
template <class Serializable1, class Serializable2>
void prepare_pipelined_write(
const Serializable1& msg1,
std::uint8_t& seqnum1,
const Serializable2& msg2,
std::uint8_t& seqnum2
)
{
reset();
seqnum1 = serialize_top_level(msg1, buffer_, seqnum1);
seqnum2 = serialize_top_level(msg2, buffer_, seqnum2);
resume(0);
}
bool done() const noexcept { return offset_ == buffer_.size(); }
span<const std::uint8_t> current_chunk() const
{
BOOST_ASSERT(!done());
BOOST_ASSERT(!buffer_.empty());
return span<const std::uint8_t>(buffer_).subspan(offset_);
}
void resume(std::size_t n)
{
std::size_t remaining = static_cast<std::size_t>(buffer_.size() - offset_);
BOOST_ASSERT(n <= remaining);
ignore_unused(remaining);
offset_ += n;
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -8,59 +8,56 @@
#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_PING_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_PING_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class ping_algo : public sansio_algorithm
class read_ping_response_algo
{
int resume_point_{0};
diagnostics* diag_;
std::uint8_t seqnum_{0};
public:
ping_algo(connection_state_data& st, ping_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag)
{
}
read_ping_response_algo(diagnostics* diag, std::uint8_t seqnum) noexcept : diag_(diag), seqnum_(seqnum) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
switch (resume_point_)
{
case 0:
// Clear diagnostics
diag_->clear();
// Send the request
BOOST_MYSQL_YIELD(resume_point_, 1, write(ping_command(), seqnum_))
// Read the response
BOOST_MYSQL_YIELD(resume_point_, 2, read(seqnum_))
// Issue a read
BOOST_MYSQL_YIELD(resume_point_, 1, st.read(seqnum_))
if (ec)
return ec;
// Process the OK packet
return st_->deserialize_ok(*diag_);
return st.deserialize_ok(*diag_);
}
return next_action();
}
};
inline run_pipeline_algo_params setup_ping_pipeline(connection_state_data& st, ping_algo_params params)
{
st.write_buffer.clear();
auto seqnum = serialize_top_level(ping_command{}, st.write_buffer);
st.shared_pipeline_stages[0] = {pipeline_stage_kind::ping, seqnum, {}};
return {
params.diag,
pipeline_request_view{st.write_buffer, {st.shared_pipeline_stages.data(), 1}},
pipeline_response_ref{}
};
}
} // namespace detail
} // namespace mysql
} // namespace boost
@@ -11,31 +11,30 @@
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
namespace detail {
class prepare_statement_algo : public sansio_algorithm
class read_prepare_statement_response_algo
{
int resume_point_{0};
diagnostics* diag_;
string_view stmt_sql_;
std::uint8_t sequence_number_{0};
unsigned remaining_meta_{0};
statement res_;
error_code process_response()
error_code process_response(connection_state_data& st)
{
prepare_stmt_response response{};
auto err = deserialize_prepare_stmt_response(st_->reader.message(), st_->flavor, response, *diag_);
auto err = deserialize_prepare_stmt_response(st.reader.message(), st.flavor, response, *diag_);
if (err)
return err;
res_ = access::construct<statement>(response.id, response.num_params);
@@ -44,12 +43,15 @@ class prepare_statement_algo : public sansio_algorithm
}
public:
prepare_statement_algo(connection_state_data& st, prepare_statement_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag), stmt_sql_(params.stmt_sql)
read_prepare_statement_response_algo(diagnostics* diag, std::uint8_t seqnum) noexcept
: diag_(diag), sequence_number_(seqnum)
{
}
next_action resume(error_code ec)
std::uint8_t& sequence_number() { return sequence_number_; }
diagnostics& diag() { return *diag_; }
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
@@ -58,34 +60,74 @@ public:
{
case 0:
// Clear diagnostics
diag_->clear();
// Send request
BOOST_MYSQL_YIELD(resume_point_, 1, write(prepare_stmt_command{stmt_sql_}, sequence_number_))
// Note: diagnostics should have been cleaned by other algos
// Read response
BOOST_MYSQL_YIELD(resume_point_, 2, read(sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 1, st.read(sequence_number_))
// Process response
ec = process_response();
ec = process_response(st);
if (ec)
return ec;
// Server sends now one packet per parameter and field.
// We ignore these for now.
for (; remaining_meta_ > 0u; --remaining_meta_)
BOOST_MYSQL_YIELD(resume_point_, 3, read(sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 2, st.read(sequence_number_))
}
return next_action();
}
statement result() const { return res_; }
statement result(const connection_state_data&) const { return res_; }
};
class prepare_statement_algo
{
int resume_point_{0};
read_prepare_statement_response_algo read_response_st_;
string_view stmt_sql_;
public:
prepare_statement_algo(prepare_statement_algo_params params) noexcept
: read_response_st_(params.diag, 0u), stmt_sql_(params.stmt_sql)
{
}
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
switch (resume_point_)
{
case 0:
// Clear diagnostics
read_response_st_.diag().clear();
// Send request
BOOST_MYSQL_YIELD(
resume_point_,
1,
st.write(prepare_stmt_command{stmt_sql_}, read_response_st_.sequence_number())
)
if (ec)
return ec;
// Read response
while (!(act = read_response_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
return act;
}
return next_action();
}
statement result(const connection_state_data& st) const { return read_response_st_.result(st); }
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP_ */
#endif
@@ -17,27 +17,23 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
namespace detail {
class quit_connection_algo : public sansio_algorithm
class quit_connection_algo
{
int resume_point_{0};
diagnostics* diag_;
std::uint8_t sequence_number_{0};
public:
quit_connection_algo(connection_state_data& st, quit_connection_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag)
{
}
quit_connection_algo(quit_connection_algo_params params) noexcept : diag_(params.diag) {}
diagnostics& diag() { return *diag_; }
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
switch (resume_point_)
{
@@ -47,20 +43,20 @@ public:
diag_->clear();
// Send quit message
BOOST_MYSQL_YIELD(resume_point_, 1, write(quit_command(), sequence_number_))
BOOST_MYSQL_YIELD(resume_point_, 1, st.write(quit_command(), sequence_number_))
// Mark the session as finished, regardless of the write result
st_->is_connected = false;
st.is_connected = false;
// If write resulted in an error, return
if (ec)
return ec;
// Shutdown SSL. MySQL doesn't always shut down SSL correctly, so we ignore this error.
if (st_->ssl == ssl_state::active)
if (st.ssl == ssl_state::active)
{
BOOST_MYSQL_YIELD(resume_point_, 2, next_action::ssl_shutdown())
st_->ssl = ssl_state::torn_down;
st.ssl = ssl_state::torn_down;
}
// Done
@@ -16,7 +16,6 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
@@ -59,52 +58,60 @@ inline error_code process_field_definition(
return proc.on_meta(coldef, diag);
}
class read_resultset_head_algo : public sansio_algorithm
class read_resultset_head_algo
{
int resume_point_{0};
read_resultset_head_algo_params params_;
diagnostics* diag_;
execution_processor* proc_;
struct state_t
{
int resume_point{0};
} state_;
public:
read_resultset_head_algo(connection_state_data& st, read_resultset_head_algo_params params) noexcept
: sansio_algorithm(st), params_(params)
read_resultset_head_algo(read_resultset_head_algo_params params) noexcept
: diag_(params.diag), proc_(params.proc)
{
}
read_resultset_head_algo_params params() const { return params_; }
void reset() { state_ = state_t{}; }
next_action resume(error_code ec)
diagnostics& diag() { return *diag_; }
execution_processor& processor() { return *proc_; }
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
switch (resume_point_)
switch (state_.resume_point)
{
case 0:
// Clear diagnostics
params_.diag->clear();
diag_->clear();
// If we're not reading head, return
if (!params_.proc->is_reading_head())
if (!proc_->is_reading_head())
return next_action();
// Read the response
BOOST_MYSQL_YIELD(resume_point_, 1, read(params_.proc->sequence_number()))
BOOST_MYSQL_YIELD(state_.resume_point, 1, st.read(proc_->sequence_number()))
// Response may be: ok_packet, err_packet, local infile request
// (not implemented), or response with fields
ec = process_execution_response(*st_, *params_.proc, st_->reader.message(), *params_.diag);
ec = process_execution_response(st, *proc_, st.reader.message(), *diag_);
if (ec)
return ec;
// Read all of the field definitions
while (params_.proc->is_reading_meta())
while (proc_->is_reading_meta())
{
// Read a message
BOOST_MYSQL_YIELD(resume_point_, 2, read(params_.proc->sequence_number()))
BOOST_MYSQL_YIELD(state_.resume_point, 2, st.read(proc_->sequence_number()))
// Process the metadata packet
ec = process_field_definition(*params_.proc, st_->reader.message(), *params_.diag);
ec = process_field_definition(*proc_, st.reader.message(), *diag_);
if (ec)
return ec;
}
@@ -18,7 +18,6 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <cstddef>
@@ -26,11 +25,17 @@ namespace boost {
namespace mysql {
namespace detail {
class read_some_rows_algo : public sansio_algorithm
class read_some_rows_algo
{
int resume_point_{0};
read_some_rows_algo_params params_;
std::size_t rows_read_{0};
diagnostics* diag_;
execution_processor* proc_;
output_ref output_;
struct state_t
{
int resume_point{0};
std::size_t rows_read{0};
} state_;
BOOST_ATTRIBUTE_NODISCARD static std::pair<error_code, std::size_t> process_some_rows(
connection_state_data& st,
@@ -88,31 +93,32 @@ class read_some_rows_algo : public sansio_algorithm
return {error_code(), read_rows};
}
execution_processor& processor() { return *params_.proc; }
public:
read_some_rows_algo(connection_state_data& st, read_some_rows_algo_params params) noexcept
: sansio_algorithm(st), params_(params)
read_some_rows_algo(read_some_rows_algo_params params) noexcept
: diag_(params.diag), proc_(params.proc), output_(params.output)
{
}
read_some_rows_algo_params params() const { return params_; }
void reset() { state_ = state_t{}; }
next_action resume(error_code ec)
const execution_processor& processor() const { return *proc_; }
execution_processor& processor() { return *proc_; }
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
switch (resume_point_)
switch (state_.resume_point)
{
case 0:
// Clear diagnostics
params_.diag->clear();
diag_->clear();
// Clear any previous use of shared fields.
// Required for the dynamic version to work.
st_->shared_fields.clear();
st.shared_fields.clear();
// If we are not reading rows, return
if (!processor().is_reading_rows())
@@ -120,17 +126,17 @@ public:
// Read at least one message. Keep parsing state, in case a previous message
// was parsed partially
BOOST_MYSQL_YIELD(resume_point_, 1, read(processor().sequence_number(), true))
BOOST_MYSQL_YIELD(state_.resume_point, 1, st.read(proc_->sequence_number(), true))
// Process messages
std::tie(ec, rows_read_) = process_some_rows(*st_, processor(), params_.output, *params_.diag);
std::tie(ec, state_.rows_read) = process_some_rows(st, *proc_, output_, *diag_);
return ec;
}
return next_action();
}
std::size_t result() const noexcept { return rows_read_; }
std::size_t result(const connection_state_data&) const { return state_.rows_read; }
};
} // namespace detail
@@ -29,16 +29,16 @@ namespace detail {
class read_some_rows_dynamic_algo : public read_some_rows_algo
{
public:
read_some_rows_dynamic_algo(connection_state_data& st, read_some_rows_dynamic_algo_params params) noexcept
: read_some_rows_algo(st, read_some_rows_algo_params{params.diag, params.exec_st, output_ref()})
read_some_rows_dynamic_algo(read_some_rows_dynamic_algo_params params) noexcept
: read_some_rows_algo(read_some_rows_algo_params{params.diag, params.exec_st, output_ref()})
{
}
rows_view result() const noexcept
rows_view result(const connection_state_data& st) const
{
std::size_t num_rows = read_some_rows_algo::result();
std::size_t num_cols = static_cast<const execution_state_impl&>(*params().proc).meta().size();
return access::construct<rows_view>(st_->shared_fields.data(), num_rows * num_cols, num_cols);
std::size_t num_rows = read_some_rows_algo::result(st);
std::size_t num_cols = static_cast<const execution_state_impl&>(processor()).meta().size();
return access::construct<rows_view>(st.shared_fields.data(), num_rows * num_cols, num_cols);
}
};
@@ -8,122 +8,80 @@
#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_RESET_CONNECTION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_RESET_CONNECTION_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/mysql/impl/internal/sansio/set_character_set.hpp>
namespace boost {
namespace mysql {
namespace detail {
class reset_connection_algo : public sansio_algorithm
class read_reset_connection_response_algo
{
int resume_point_{0};
diagnostics* diag_;
character_set charset_;
std::uint8_t reset_seqnum_{0};
std::uint8_t set_names_seqnum_{0};
error_code stored_ec_;
// true if we need to pipeline a SET NAMES with the reset request
bool has_charset() const { return !charset_.name.empty(); }
next_action compose_request()
{
if (has_charset())
{
// Compose the SET NAMES statement
auto query = compose_set_names(charset_);
if (query.has_error())
return query.error();
// Compose the pipeline
st_->writer.prepare_pipelined_write(
reset_connection_command{},
reset_seqnum_,
query_command{query.value()},
set_names_seqnum_
);
// Success
return next_action::write({});
}
else
{
// Just compose the reset connection request
return write(reset_connection_command{}, reset_seqnum_);
}
}
std::uint8_t seqnum_{0};
public:
reset_connection_algo(connection_state_data& st, reset_connection_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag), charset_(params.charset)
read_reset_connection_response_algo(diagnostics* diag, std::uint8_t seqnum) noexcept
: diag_(diag), seqnum_(seqnum)
{
}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
switch (resume_point_)
{
case 0:
// Clear diagnostics
diag_->clear();
// Send the request
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request())
// Read the reset response
BOOST_MYSQL_YIELD(resume_point_, 2, read(reset_seqnum_))
BOOST_MYSQL_YIELD(resume_point_, 1, st.read(seqnum_))
if (ec)
return ec;
// Verify it's what we expected
stored_ec_ = st_->deserialize_ok(*diag_);
if (!stored_ec_)
ec = st.deserialize_ok(*diag_);
if (!ec)
{
// Reset was successful. Resetting changes the connection's character set
// to the server's default, which is an unknown value that doesn't have to match
// what was specified in handshake. As a safety measure, clear the current charset
st_->current_charset = character_set{};
st.current_charset = character_set{};
}
if (has_charset())
{
// We issued a SET NAMES too, read its response
BOOST_MYSQL_YIELD(resume_point_, 3, read(set_names_seqnum_))
// Verify it's what we expected. Don't overwrite diagnostics if reset failed
ec = st_->deserialize_ok(stored_ec_ ? st_->shared_diag : *diag_);
if (!ec)
{
// Set the character set to the new known value
st_->current_charset = charset_;
}
// Set the return value if there is no error code already stored
if (!stored_ec_)
stored_ec_ = ec;
}
return stored_ec_;
// Done
return ec;
}
return next_action();
}
};
inline run_pipeline_algo_params setup_reset_connection_pipeline(
connection_state_data& st,
reset_connection_algo_params params
)
{
st.write_buffer.clear();
st.shared_pipeline_stages[0] = {
pipeline_stage_kind::reset_connection,
serialize_top_level(reset_connection_command{}, st.write_buffer),
{}
};
return {
params.diag,
pipeline_request_view{st.write_buffer, {st.shared_pipeline_stages.data(), 1}},
pipeline_response_ref()
};
}
} // namespace detail
} // namespace mysql
} // namespace boost
@@ -0,0 +1,214 @@
//
// 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_IMPL_INTERNAL_SANSIO_RUN_PIPELINE_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_RUN_PIPELINE_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/is_fatal_error.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/execute.hpp>
#include <boost/mysql/impl/internal/sansio/ping.hpp>
#include <boost/mysql/impl/internal/sansio/prepare_statement.hpp>
#include <boost/mysql/impl/internal/sansio/reset_connection.hpp>
#include <boost/mysql/impl/internal/sansio/set_character_set.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class run_pipeline_algo
{
union any_read_algo
{
std::nullptr_t nothing;
read_execute_response_algo execute;
read_prepare_statement_response_algo prepare_statement;
read_reset_connection_response_algo reset_connection;
read_ping_response_algo ping;
read_set_character_set_response_algo set_character_set;
any_read_algo() noexcept : nothing{} {}
};
diagnostics* diag_;
span<const std::uint8_t> request_buffer_;
span<const pipeline_request_stage> stages_;
pipeline_response_ref response_;
int resume_point_{0};
std::size_t current_stage_index_{0};
error_code pipeline_ec_; // Result of the entire operation
bool has_hatal_error_{}; // If true, fail further stages with pipeline_ec_
any_read_algo read_response_algo_;
diagnostics temp_diag_;
void setup_current_stage(const connection_state_data& st)
{
// Reset previous data
temp_diag_.clear();
// Setup read algo
auto stage = stages_[current_stage_index_];
switch (stage.kind)
{
case pipeline_stage_kind::execute:
{
auto& processor = response_.get_processor(current_stage_index_);
processor.reset(stage.stage_specific.enc, st.meta_mode);
processor.sequence_number() = stage.seqnum;
read_response_algo_.execute = {&temp_diag_, &processor};
break;
}
case pipeline_stage_kind::prepare_statement:
read_response_algo_.prepare_statement = {&temp_diag_, stage.seqnum};
break;
case pipeline_stage_kind::close_statement:
// Close statement doesn't have a response
read_response_algo_.nothing = nullptr;
break;
case pipeline_stage_kind::set_character_set:
read_response_algo_.set_character_set = {&temp_diag_, stage.stage_specific.charset, stage.seqnum};
break;
case pipeline_stage_kind::reset_connection:
read_response_algo_.reset_connection = {&temp_diag_, stage.seqnum};
break;
case pipeline_stage_kind::ping: read_response_algo_.ping = {&temp_diag_, stage.seqnum}; break;
default: BOOST_ASSERT(false);
}
}
void on_stage_finished(const connection_state_data& st, error_code stage_ec)
{
if (stage_ec)
{
// If the error was fatal, fail successive stages.
// This error is the result of the operation
if (is_fatal_error(stage_ec))
{
pipeline_ec_ = stage_ec;
*diag_ = temp_diag_;
has_hatal_error_ = true;
}
else if (!pipeline_ec_)
{
// In the absence of fatal errors, the first error we encounter is the result of the operation
pipeline_ec_ = stage_ec;
*diag_ = temp_diag_;
}
// Propagate the error
response_.set_error(current_stage_index_, stage_ec, std::move(temp_diag_));
}
else
{
if (stages_[current_stage_index_].kind == pipeline_stage_kind::prepare_statement)
{
// Propagate results. We don't support prepare statements
// with responses having has_value() == false
response_.set_result(current_stage_index_, read_response_algo_.prepare_statement.result(st));
}
}
}
next_action resume_read_algo(connection_state_data& st, error_code ec)
{
switch (stages_[current_stage_index_].kind)
{
case pipeline_stage_kind::execute: return read_response_algo_.execute.resume(st, ec);
case pipeline_stage_kind::prepare_statement:
return read_response_algo_.prepare_statement.resume(st, ec);
case pipeline_stage_kind::reset_connection:
return read_response_algo_.reset_connection.resume(st, ec);
case pipeline_stage_kind::set_character_set:
return read_response_algo_.set_character_set.resume(st, ec);
case pipeline_stage_kind::ping: return read_response_algo_.ping.resume(st, ec);
case pipeline_stage_kind::close_statement: return next_action(); // has no response
default: BOOST_ASSERT(false); return next_action();
}
}
public:
run_pipeline_algo(run_pipeline_algo_params params) noexcept
: diag_(params.diag),
request_buffer_(params.request.buffer),
stages_(params.request.stages),
response_(params.response)
{
}
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
switch (resume_point_)
{
case 0:
// Clear previous state
diag_->clear();
response_.setup(stages_);
// If the request is empty, don't do anything
if (stages_.empty())
break;
// Write the request. use_ssl is attached by top_level_algo
BOOST_MYSQL_YIELD(resume_point_, 1, next_action::write({request_buffer_, false}))
// If writing the request failed, fail all the stages with the given error code
if (ec)
{
pipeline_ec_ = ec;
has_hatal_error_ = true;
}
// For each stage
for (; current_stage_index_ < stages_.size(); ++current_stage_index_)
{
// If there was a fatal error, just set the error and move forward
if (has_hatal_error_)
{
response_.set_error(current_stage_index_, pipeline_ec_, diagnostics(*diag_));
continue;
}
// Setup the stage
setup_current_stage(st);
// Run it until completion
ec.clear();
while (!(act = resume_read_algo(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
// Process the stage's result
on_stage_finished(st, act.error());
}
}
return pipeline_ec_;
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -1,52 +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_IMPL_INTERNAL_SANSIO_SANSIO_ALGORITHM_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_SANSIO_ALGORITHM_HPP
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
namespace boost {
namespace mysql {
namespace detail {
class sansio_algorithm
{
protected:
connection_state_data* st_;
next_action read(std::uint8_t& seqnum, bool keep_parsing_state = false)
{
// buffer is attached by top_level_algo
st_->reader.prepare_read(seqnum, keep_parsing_state);
return next_action::read(next_action::read_args_t{{}, false});
}
template <class Serializable>
next_action write(const Serializable& msg, std::uint8_t& seqnum)
{
// buffer is attached by top_level_algo
st_->writer.prepare_write(msg, seqnum);
return next_action::write(next_action::write_args_t{{}, false});
}
sansio_algorithm(connection_state_data& st) noexcept : st_(&st) {}
public:
const connection_state_data& conn_state() const noexcept { return *st_; }
connection_state_data& conn_state() noexcept { return *st_; }
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -9,10 +9,9 @@
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_SET_CHARACTER_SET_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/format_sql.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/next_action.hpp>
@@ -20,54 +19,96 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/protocol/deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/system/result.hpp>
#include <string>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
// Securely compose a SET NAMES statement. This function
// is also used by the pipelined version of reset_connection
inline system::result<std::string> compose_set_names(const character_set& charset)
// Securely compose a SET NAMES statement
inline system::result<std::string> compose_set_names(character_set charset)
{
// The character set should have a non-empty name
// The character set should have a non-empty name (should not be default-constructed)
BOOST_ASSERT(!charset.name.empty());
// NULL characters cause MySQL to truncate the character set name string
if (charset.name.contains('\0'))
return client_errc::invalid_encoding;
// For security, if the character set has non-ascii characters in it name, reject it.
format_context ctx(format_options{ascii_charset, true});
ctx.append_raw("SET NAMES ").append_value(charset.name);
return std::move(ctx).get();
}
class set_character_set_algo : public sansio_algorithm
class read_set_character_set_response_algo
{
int resume_point_{0};
diagnostics* diag_;
character_set charset_;
std::uint8_t seqnum_{0};
next_action compose_request()
public:
read_set_character_set_response_algo(diagnostics* diag, character_set charset, std::uint8_t seqnum)
: diag_(diag), charset_(charset), seqnum_(seqnum)
{
auto q = compose_set_names(charset_);
}
character_set charset() const { return charset_; }
diagnostics& diag() { return *diag_; }
std::uint8_t& sequence_number() { return seqnum_; }
next_action resume(connection_state_data& st, error_code ec)
{
// SET NAMES never returns rows. Using execute requires us to allocate
// a results object, which we can avoid by simply sending the query and reading the OK response.
switch (resume_point_)
{
case 0:
// Read the response
BOOST_MYSQL_YIELD(resume_point_, 1, st.read(seqnum_))
if (ec)
return ec;
// Verify it's what we expected
ec = st.deserialize_ok(*diag_);
if (ec)
return ec;
// If we were successful, update the character set
st.current_charset = charset_;
}
return next_action();
}
};
class set_character_set_algo
{
int resume_point_{0};
read_set_character_set_response_algo read_response_st_;
next_action compose_request(connection_state_data& st)
{
auto q = compose_set_names(read_response_st_.charset());
if (q.has_error())
return q.error();
return write(query_command{q.value()}, seqnum_);
return st.write(query_command{q.value()}, read_response_st_.sequence_number());
}
public:
set_character_set_algo(connection_state_data& st, set_character_set_algo_params params) noexcept
: sansio_algorithm(st), diag_(params.diag), charset_(params.charset)
set_character_set_algo(set_character_set_algo_params params) noexcept
: read_response_st_(params.diag, params.charset, 0u)
{
}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
if (ec)
return ec;
next_action act;
// SET NAMES never returns rows. Using execute requires us to allocate
// a results object, which we can avoid by simply sending the query and reading the OK response.
@@ -76,21 +117,17 @@ public:
case 0:
// Setup
diag_->clear();
read_response_st_.diag().clear();
// Send the execution request
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request())
// Read the response
BOOST_MYSQL_YIELD(resume_point_, 2, read(seqnum_))
// Verify it's what we expected
ec = st_->deserialize_ok(*diag_);
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request(st))
if (ec)
return ec;
// If we were successful, update the character set
st_->current_charset = charset_;
// Read the response
while (!(act = read_response_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
return act;
}
return next_action();
@@ -20,7 +20,6 @@
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/read_resultset_head.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
namespace boost {
namespace mysql {
@@ -39,37 +38,35 @@ inline resultset_encoding get_encoding(const any_execution_request& req)
return req.is_query ? resultset_encoding::text : resultset_encoding::binary;
}
class start_execution_algo : public sansio_algorithm
class start_execution_algo
{
int resume_point_{0};
read_resultset_head_algo read_head_st_;
any_execution_request req_;
std::uint8_t& seqnum() { return processor().sequence_number(); }
execution_processor& processor() { return *read_head_st_.params().proc; }
diagnostics& diag() { return *read_head_st_.params().diag; }
execution_processor& processor() { return read_head_st_.processor(); }
diagnostics& diag() { return read_head_st_.diag(); }
next_action compose_request()
next_action compose_request(connection_state_data& st)
{
if (req_.is_query)
{
return write(query_command{req_.data.query}, seqnum());
return st.write(query_command{req_.data.query}, seqnum());
}
else
{
return write(execute_stmt_command{req_.data.stmt.stmt.id(), req_.data.stmt.params}, seqnum());
return st.write(execute_stmt_command{req_.data.stmt.stmt.id(), req_.data.stmt.params}, seqnum());
}
}
public:
start_execution_algo(connection_state_data& st, start_execution_algo_params params) noexcept
: sansio_algorithm(st),
read_head_st_(st, read_resultset_head_algo_params{params.diag, params.proc}),
req_(params.req)
start_execution_algo(start_execution_algo_params params) noexcept
: read_head_st_(read_resultset_head_algo_params{params.diag, params.proc}), req_(params.req)
{
}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
next_action act;
@@ -86,16 +83,16 @@ public:
return ec;
// Reset the processor
processor().reset(get_encoding(req_), st_->meta_mode);
processor().reset(get_encoding(req_), st.meta_mode);
// Send the execution request
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request())
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request(st))
if (ec)
return ec;
// Read the first resultset's head and return its result
while (!(act = read_head_st_.resume(ec)).is_done())
while (!(act = read_head_st_.resume(st, ec)).is_done())
BOOST_MYSQL_YIELD(resume_point_, 2, act)
return act;
}
@@ -14,9 +14,11 @@
#include <boost/mysql/impl/internal/coroutine.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
#ifdef BOOST_USE_VALGRIND
#include <valgrind/memcheck.h>
@@ -36,22 +38,25 @@ inline void valgrind_make_mem_defined(const void* data, std::size_t size)
inline void valgrind_make_mem_defined(const void*, std::size_t) noexcept {}
#endif
// InnerAlgo should have
// Constructible from the args forwarded by the ctor.
// next_action resume(connection_state_data&, error_code);
// AlgoParams::result_type result(const connection_state_data&) const; // if AlgoParams::result_type != void
template <class InnerAlgo>
class top_level_algo
{
int resume_point_{0};
connection_state_data* st_;
InnerAlgo algo_;
connection_state_data& conn_state() noexcept { return algo_.conn_state(); }
span<const std::uint8_t> bytes_to_write_;
public:
template <class... Args>
top_level_algo(connection_state_data& st_data, Args&&... args)
: algo_(st_data, std::forward<Args>(args)...)
top_level_algo(connection_state_data& st, Args&&... args) : st_(&st), algo_(std::forward<Args>(args)...)
{
}
const InnerAlgo& inner_algo() const noexcept { return algo_; }
const InnerAlgo& inner_algo() const { return algo_; }
next_action resume(error_code ec, std::size_t bytes_transferred)
{
@@ -65,48 +70,47 @@ public:
while (true)
{
// Run the op
act = algo_.resume(ec);
act = algo_.resume(*st_, ec);
// Check next action
if (act.is_done())
{
return act;
}
else if (act.type() == next_action::type_t::read)
else if (act.type() == next_action_type::read)
{
// Read until a complete message is received
// (may be zero times if cached)
while (!conn_state().reader.done() && !ec)
while (!st_->reader.done() && !ec)
{
conn_state().reader.prepare_buffer();
st_->reader.prepare_buffer();
BOOST_MYSQL_YIELD(
resume_point_,
1,
next_action::read({conn_state().reader.buffer(), conn_state().ssl_active()})
next_action::read({st_->reader.buffer(), st_->ssl_active()})
)
valgrind_make_mem_defined(conn_state().reader.buffer().data(), bytes_transferred);
conn_state().reader.resume(bytes_transferred);
valgrind_make_mem_defined(st_->reader.buffer().data(), bytes_transferred);
st_->reader.resume(bytes_transferred);
}
// Check for errors
if (!ec)
ec = conn_state().reader.error();
ec = st_->reader.error();
// We've got a message, continue
}
else if (act.type() == next_action::type_t::write)
else if (act.type() == next_action_type::write)
{
// Write until a complete message was written
while (!conn_state().writer.done() && !ec)
bytes_to_write_ = act.write_args().buffer;
while (!bytes_to_write_.empty() && !ec)
{
BOOST_MYSQL_YIELD(
resume_point_,
2,
next_action::write(
{conn_state().writer.current_chunk(), conn_state().ssl_active()}
)
next_action::write({bytes_to_write_, st_->ssl_active()})
)
conn_state().writer.resume(bytes_transferred);
bytes_to_write_ = bytes_to_write_.subspan(bytes_transferred);
}
// We fully wrote a message, continue
+216
View File
@@ -0,0 +1,216 @@
//
// 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_IMPL_PIPELINE_HPP
#define BOOST_MYSQL_IMPL_PIPELINE_HPP
#pragma once
#include <boost/mysql/pipeline.hpp>
#include <cstddef>
#include <type_traits>
namespace boost {
namespace mysql {
namespace detail {
// Stage concepts
template <>
struct is_pipeline_stage_type<execute_stage> : std::true_type
{
};
template <>
struct is_pipeline_stage_type<prepare_statement_stage> : std::true_type
{
};
template <>
struct is_pipeline_stage_type<close_statement_stage> : std::true_type
{
};
template <>
struct is_pipeline_stage_type<reset_connection_stage> : std::true_type
{
};
template <>
struct is_pipeline_stage_type<set_character_set_stage> : std::true_type
{
};
// Request concepts
template <>
struct is_pipeline_request_type<pipeline_request> : std::true_type
{
};
template <class... PipelineStageType>
struct is_pipeline_request_type<static_pipeline_request<PipelineStageType...>> : std::true_type
{
};
// Response traits
template <>
struct pipeline_response_traits<std::vector<any_stage_response>>
{
using response_type = std::vector<any_stage_response>;
static void setup(response_type& self, span<const pipeline_request_stage> request)
{
// Create as many response items as request stages
self.resize(request.size());
// Setup them
for (std::size_t i = 0u; i < request.size(); ++i)
{
// Execution stages need to be initialized to results objects.
// Otherwise, clear any previous content
auto& impl = access::get_impl(self[i]);
if (request[i].kind == pipeline_stage_kind::execute)
impl.emplace_results();
else
impl.emplace_error();
}
}
static execution_processor& get_processor(response_type& self, std::size_t idx)
{
BOOST_ASSERT(idx < self.size());
return access::get_impl(self[idx]).get_processor();
}
static void set_result(response_type& self, std::size_t idx, statement stmt)
{
BOOST_ASSERT(idx < self.size());
access::get_impl(self[idx]).set_result(stmt);
}
static void set_error(response_type& self, std::size_t idx, error_code ec, diagnostics&& diag)
{
BOOST_ASSERT(idx < self.size());
access::get_impl(self[idx]).set_error(ec, std::move(diag));
}
};
// Index a tuple at runtime
template <std::size_t I, class R>
struct tuple_index_visitor
{
template <class... T, class Fn, class... Args>
static R invoke(std::tuple<T...>& t, std::size_t i, Fn f, Args&&... args)
{
return i == I ? f(std::get<I>(t), std::forward<Args>(args)...)
: tuple_index_visitor<I - 1, R>::invoke(t, i, f, std::forward<Args>(args)...);
}
};
template <class R>
struct tuple_index_visitor<0u, R>
{
template <class... T, class Fn, class... Args>
static R invoke(std::tuple<T...>& t, std::size_t i, Fn f, Args&&... args)
{
BOOST_ASSERT(i == 0u);
ignore_unused(i);
return f(std::get<0u>(t), std::forward<Args>(args)...);
}
};
template <class... T, class Fn, class... Args>
auto tuple_index(std::tuple<T...>& t, std::size_t i, Fn f, Args&&... args)
-> decltype(f(std::get<0u>(t), std::forward<Args>(args)...))
{
static_assert(sizeof...(T) > 0u, "Empty tuples not allowed");
BOOST_ASSERT(i < sizeof...(T));
using result_type = decltype(f(std::get<0u>(t), std::forward<Args>(args)...));
return tuple_index_visitor<sizeof...(T) - 1, result_type>::invoke(t, i, f, std::forward<Args>(args)...);
}
// Concrete visitors
struct stage_reset_visitor
{
void operator()(execute_stage::response_type& value) const { value.emplace(); }
void operator()(prepare_statement_stage::response_type& value) const { value.emplace(); }
void operator()(errcode_with_diagnostics& value) const
{
value.code.clear();
value.diag.clear();
}
};
struct stage_get_processor_visitor
{
execution_processor* operator()(execute_stage::response_type& value) const
{
return &access::get_impl(*value);
}
template <class PipelineStageType>
execution_processor* operator()(PipelineStageType&) const
{
BOOST_ASSERT(false);
return nullptr;
}
};
struct stage_set_result_visitor
{
void operator()(prepare_statement_stage::response_type& r, statement s) const { r = s; }
template <class PipelineStageType>
void operator()(PipelineStageType&, statement) const
{
BOOST_ASSERT(false);
}
};
struct stage_set_error_visitor
{
template <class PipelineStageType>
void operator()(PipelineStageType& r, error_code ec, diagnostics&& diag) const
{
r = errcode_with_diagnostics{ec, std::move(diag)};
}
};
template <class... PipelineStageResponseType>
struct pipeline_response_traits<std::tuple<PipelineStageResponseType...>>
{
using response_type = std::tuple<PipelineStageResponseType...>;
static void setup(response_type& self, span<const pipeline_request_stage> request)
{
BOOST_ASSERT(request.size() == sizeof...(PipelineStageResponseType));
ignore_unused(request);
mp11::tuple_for_each(self, stage_reset_visitor{});
}
static execution_processor& get_processor(response_type& self, std::size_t idx)
{
return *tuple_index(self, idx, stage_get_processor_visitor{});
}
static void set_result(response_type& self, std::size_t idx, statement stmt)
{
tuple_index(self, idx, stage_set_result_visitor{}, stmt);
}
static void set_error(response_type& self, std::size_t idx, error_code ec, diagnostics&& diag)
{
tuple_index(self, idx, stage_set_error_visitor{}, ec, std::move(diag));
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+179
View File
@@ -0,0 +1,179 @@
//
// 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_IMPL_PIPELINE_IPP
#define BOOST_MYSQL_IMPL_PIPELINE_IPP
#pragma once
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/mysql/impl/internal/sansio/set_character_set.hpp>
#include <boost/core/span.hpp>
#include <boost/throw_exception.hpp>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace boost {
namespace mysql {
namespace detail {
inline std::uint8_t serialize_execute_statement(
statement stmt,
span<const field_view> params,
std::vector<std::uint8_t>& buff
)
{
if (params.size() != stmt.num_params())
{
BOOST_THROW_EXCEPTION(
std::invalid_argument("Wrong number of actual parameters supplied to a prepared statement")
);
}
return serialize_top_level(execute_stmt_command{stmt.id(), params}, buff);
}
// TODO: consider using Boost.Container (or adding tests)
inline std::uint8_t serialize_execute_stmt_tuple(
statement stmt,
span<const execute_stage::statement_param_arg> params,
std::vector<std::uint8_t>& buff
)
{
constexpr std::size_t stack_fields = 64u;
if (params.size() <= stack_fields)
{
std::array<field_view, stack_fields> storage;
for (std::size_t i = 0; i < params.size(); ++i)
storage[i] = access::get_impl(params[i]);
return detail::serialize_execute_statement(stmt, {storage.data(), params.size()}, buff);
}
else
{
std::vector<field_view> storage;
storage.reserve(params.size());
for (auto p : params)
storage.push_back(access::get_impl(p));
return detail::serialize_execute_statement(stmt, storage, buff);
}
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::detail::pipeline_request_stage boost::mysql::execute_stage::create(
std::vector<std::uint8_t>& buff
) const
{
switch (type_)
{
case type_query:
return {
detail::pipeline_stage_kind::execute,
detail::serialize_top_level(detail::query_command{data_.query}, buff),
detail::resultset_encoding::text
};
case type_stmt_tuple:
{
return {
detail::pipeline_stage_kind::execute,
detail::serialize_execute_stmt_tuple(data_.stmt_tuple.stmt, data_.stmt_tuple.params, buff),
detail::resultset_encoding::binary
};
}
case type_stmt_range:
return {
detail::pipeline_stage_kind::execute,
detail::serialize_execute_statement(data_.stmt_range.stmt, data_.stmt_range.params, buff),
detail::resultset_encoding::binary
};
default: BOOST_ASSERT(false); return {};
}
}
boost::mysql::detail::pipeline_request_stage boost::mysql::prepare_statement_stage::create(
std::vector<std::uint8_t>& buffer
) const
{
return {
detail::pipeline_stage_kind::prepare_statement,
detail::serialize_top_level(detail::prepare_stmt_command{stmt_sql_}, buffer),
{}
};
}
boost::mysql::detail::pipeline_request_stage boost::mysql::close_statement_stage::create(
std::vector<std::uint8_t>& buffer
) const
{
return {
detail::pipeline_stage_kind::close_statement,
detail::serialize_top_level(detail::close_stmt_command{stmt_id_}, buffer),
{}
};
}
boost::mysql::detail::pipeline_request_stage boost::mysql::reset_connection_stage::create(
std::vector<std::uint8_t>& buffer
) const
{
return {
detail::pipeline_stage_kind::reset_connection,
detail::serialize_top_level(detail::reset_connection_command{}, buffer),
{}
};
}
boost::mysql::detail::pipeline_request_stage boost::mysql::set_character_set_stage::create(
std::vector<std::uint8_t>& buffer
) const
{
auto q = detail::compose_set_names(charset_);
if (q.has_error())
{
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid character set name"));
}
return {
detail::pipeline_stage_kind::set_character_set,
detail::serialize_top_level(detail::query_command{*q}, buffer),
charset_
};
}
void boost::mysql::any_stage_response::check_has_results() const
{
if (!has_results())
{
BOOST_THROW_EXCEPTION(
std::invalid_argument("any_stage_response::as_results: object doesn't contain results")
);
}
}
boost::mysql::statement boost::mysql::any_stage_response::as_statement() const
{
if (!has_statement())
{
BOOST_THROW_EXCEPTION(
std::invalid_argument("any_stage_response::as_statement: object doesn't contain a statement")
);
}
return variant2::unsafe_get<1>(impl_.value);
}
#endif
+796
View File
@@ -0,0 +1,796 @@
//
// 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_PIPELINE_HPP
#define BOOST_MYSQL_PIPELINE_HPP
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/pipeline_concepts.hpp>
#include <boost/mysql/detail/writable_field_traits.hpp>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/core/span.hpp>
#include <boost/mp11/tuple.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/system/result.hpp>
#include <boost/variant2/variant.hpp>
#include <array>
#include <cstdint>
#include <initializer_list>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace mysql {
#ifndef BOOST_MYSQL_DOXYGEN
namespace detail {
struct pipeline_stage_access
{
template <class PipelineStageType>
static pipeline_request_stage create(PipelineStageType stage, std::vector<std::uint8_t>& buff)
{
return stage.create(buff);
}
};
} // namespace detail
#endif
/**
* \brief (EXPERIMENTAL) Pipeline stage type to execute text queries and prepared statements.
* \details
* To be used with \ref pipeline_request or \ref static_pipeline_request.
* Has effects equivalent to \ref any_connection::execute.
*
* \par Object lifetimes
* Like all stage types, this is a view type. Parameters required to run the stage
* are stored internally as views. Objects of this type should only be used as
* function or constructor parameters.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class execute_stage
{
public:
/**
* \brief A single statement parameter, to be used in initializer lists.
* \details
* Applies lightweight type-erasure to types satisfying the `WritableField` concept,
* so they can be passed in initializer lists.
*
* \par Object lifetimes
* This is a view type, and should only be used in initializer lists, as a function argument.
*/
class statement_param_arg
{
field_view impl_;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::access;
#endif
public:
/**
* \brief Constructor.
* \details
* Can be passed any type satisfying `WritableField`.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Object lifetimes
* No copies of `f` are performed. This type should only be used in initializer lists, to avoid
* lifetime issues.
*/
template <
BOOST_MYSQL_WRITABLE_FIELD WritableField
#ifndef BOOST_MYSQL_DOXYGEN
,
class = typename std::enable_if<detail::is_writable_field<WritableField>::value>::type
#endif
>
statement_param_arg(const WritableField& f) noexcept : impl_(detail::to_field(f))
{
}
};
/**
* \brief Constructs a stage to execute a text query.
* \details
* Adding `*this` to a pipeline creates a stage that will run
* `query` as a SQL query, as per \ref any_connection::execute.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Object lifetimes
* No copies of `query` are performed. This type should only be used
* as a function argument to avoid lifetime issues.
*/
execute_stage(string_view query) noexcept : type_(type_query), data_(query) {}
/**
* \brief Constructs a stage to execute a prepared statement.
* \details
* Adding `*this` to a pipeline creates a stage that will run
* `stmt` bound to any parameters passed in `params`, as per \ref any_connection::execute
* and \ref statement::bind. For example, `execute_stage(stmt, {42, "John"})` has
* effects equivalent to `conn.execute(stmt.bind(42, "John"))`.
* \n
* If your statement doesn't take any parameters, pass an empty initializer list
* as `params`.
*
* \par Exception safety
* No-throw guarantee. \n
* If the supplied number of parameters doesn't match the statement's number of parameters
* (i.e. `stmt.num_params() != params.size()`), a `std::invalid_argument` exception will
* be thrown when the stage is added to the pipeline (\ref pipeline_request::add or
* \ref static_pipeline_request::static_pipeline_request).
*
* \par Object lifetimes
* No copies of `params` or the values they point to are performed.
* This type should only be used as a function argument to avoid lifetime issues.
*/
execute_stage(statement stmt, std::initializer_list<statement_param_arg> params) noexcept
: type_(type_stmt_tuple), data_(stmt, {params.begin(), params.size()})
{
}
/**
* \brief Constructs a stage to execute a prepared statement.
* \details
* Adding `*this` to a pipeline creates a stage that will run
* `stmt` bound to the parameter range passed in `params`, as per \ref any_connection::execute
* and \ref statement::bind. For example, `execute_stage(stmt, params_array)` has
* effects equivalent to `conn.execute(stmt.bind(params_array.begin(), params_array.end()))`.
* \n
* Use this signature when the number of parameters that your statement takes is not
* known at compile-time.
*
* \par Exception safety
* No-throw guarantee. \n
* If the supplied number of parameters doesn't match the statement's number of parameters
* (i.e. `stmt.num_params() != params.size()`), a `std::invalid_argument` exception will
* be raised when the stage is added to the pipeline (\ref pipeline_request::add or
* \ref static_pipeline_request::static_pipeline_request).
*
* \par Object lifetimes
* No copies of `params` or the values they point to are performed.
* This type should only be used as a function argument to avoid lifetime issues.
*/
execute_stage(statement stmt, span<const field_view> params) noexcept
: type_(type_stmt_range), data_(stmt, params)
{
}
/**
* \brief The response type used by the static interface.
* \details
* A `boost::system::result` that contains a \ref results on success and
* a \ref errcode_with_diagnostics on failure.
*/
using response_type = system::result<results, errcode_with_diagnostics>;
private:
enum type_t
{
type_query,
type_stmt_tuple,
type_stmt_range
};
union data_t
{
string_view query;
struct
{
statement stmt;
span<const statement_param_arg> params;
} stmt_tuple;
struct
{
statement stmt;
span<const field_view> params;
} stmt_range;
data_t(string_view q) noexcept : query(q) {}
data_t(statement s, span<const statement_param_arg> params) noexcept : stmt_tuple{s, params} {}
data_t(statement s, span<const field_view> params) noexcept : stmt_range{s, params} {}
};
type_t type_;
data_t data_;
BOOST_MYSQL_DECL
detail::pipeline_request_stage create(std::vector<std::uint8_t>& buffer) const;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::pipeline_stage_access;
#endif
};
/**
* \brief (EXPERIMENTAL) Pipeline stage type to prepare statements.
* \details
* To be used with \ref pipeline_request or \ref static_pipeline_request.
* Has effects equivalent to \ref any_connection::prepare_statement.
*
* \par Object lifetimes
* Like all stage types, this is a view type. Parameters required to run the stage
* are stored internally as views. Objects of this type should only be used as
* function or constructor parameters.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class prepare_statement_stage
{
string_view stmt_sql_;
BOOST_MYSQL_DECL
detail::pipeline_request_stage create(std::vector<std::uint8_t>& buffer) const;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::pipeline_stage_access;
#endif
public:
/**
* \brief Constructor.
* \details
* Adding a `prepare_statement_stage(stmt_sql)` to a pipeline
* creates a stage with effects equivalent to `conn.prepare_statement(stmt_sql)`.
*
* \par Exception safety
* No-throw guarantee.
*
* \par Object lifetimes
* No copies of `stmt_sql` are performed. This type should only be used
* as a function argument to avoid lifetime issues.
*/
prepare_statement_stage(string_view stmt_sql) noexcept : stmt_sql_(stmt_sql) {}
/**
* \brief The response type used by the static interface.
* \details
* A `boost::system::result` that contains a \ref statement on success and
* a \ref errcode_with_diagnostics on failure.
*/
using response_type = system::result<statement, errcode_with_diagnostics>;
};
/**
* \brief (EXPERIMENTAL) Pipeline stage type to close statements.
* \details
* To be used with \ref pipeline_request or \ref static_pipeline_request.
* Has effects equivalent to \ref any_connection::close_statement.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class close_statement_stage
{
std::uint32_t stmt_id_;
BOOST_MYSQL_DECL
detail::pipeline_request_stage create(std::vector<std::uint8_t>& buffer) const;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::pipeline_stage_access;
#endif
public:
/**
* \brief Constructor.
* \details
* Adding a `close_statement_stage(stmt)` to a pipeline
* creates a stage with effects equivalent to `conn.close_statement(stmt)`.
*
* \par Exception safety
* No-throw guarantee.
*/
close_statement_stage(statement stmt) noexcept : stmt_id_(stmt.id()) {}
/// The response type used by the static interface.
using response_type = errcode_with_diagnostics;
};
/**
* \brief (EXPERIMENTAL) Pipeline stage type to reset session state.
* \details
* To be used with \ref pipeline_request or \ref static_pipeline_request.
* Has effects equivalent to \ref any_connection::reset_connection.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class reset_connection_stage
{
BOOST_MYSQL_DECL
detail::pipeline_request_stage create(std::vector<std::uint8_t>& buffer) const;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::pipeline_stage_access;
#endif
public:
/**
* \brief Constructor.
* \details
* Adding a `reset_connection_stage()` to a pipeline
* creates a stage with effects equivalent to `conn.reset_connection()`.
*
* \par Exception safety
* No-throw guarantee.
*/
reset_connection_stage() = default;
/// The response type used by the static interface.
using response_type = errcode_with_diagnostics;
};
/**
* \brief (EXPERIMENTAL) Pipeline stage type to set the connection's character set.
* \details
* To be used with \ref pipeline_request or \ref static_pipeline_request.
* Has effects equivalent to \ref any_connection::set_character_set.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class set_character_set_stage
{
character_set charset_;
BOOST_MYSQL_DECL
detail::pipeline_request_stage create(std::vector<std::uint8_t>& buffer) const;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::pipeline_stage_access;
#endif
public:
/**
* \brief Constructor.
* \details
* Adding `*this` to a pipeline creates a stage with effects
* equivalent to `conn.set_character_set(charset)`.
*
* \par Exception safety
* No-throw guarantee. \n
* If the supplied character set name is not valid (i.e. `charset.name` contains
* non-ASCII characters), a `std::invalid_argument` exception will
* be raised when the stage is added to the pipeline (\ref pipeline_request::add or
* \ref static_pipeline_request::static_pipeline_request). This never happens with
* the character sets provided by the library. The check is performed as a hardening measure.
*/
set_character_set_stage(character_set charset) noexcept : charset_(charset) {}
/// The response type used by the static interface.
using response_type = errcode_with_diagnostics;
};
/**
* \brief (EXPERIMENTAL) A variant-like type holding the response of a single pipeline stage.
* \details
* When running dynamic pipelines with \ref pipeline_request, this type is used
* to hold individual stage responses.
* \n
* This is a variant-like type, similar to `boost::system::result`. At any point in time,
* it can contain: \n
* \li A \ref statement. Will happen if the stage was a prepare statement that succeeded.
* \li A \ref results. Will happen if the stage was a query or statement execution that succeeded.
* \li A \ref errcode_with_diagnostics. Will happen if the stage failed, or if it succeeded but
* it doesn't yield a value (as in close statement, reset connection and set character set).
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class any_stage_response
{
#ifndef BOOST_MYSQL_DOXYGEN
struct
{
variant2::variant<errcode_with_diagnostics, statement, results> value;
void emplace_results() { value.emplace<results>(); }
void emplace_error() { value.emplace<errcode_with_diagnostics>(); }
detail::execution_processor& get_processor()
{
return detail::access::get_impl(variant2::unsafe_get<2>(value));
}
void set_result(statement s) { value = s; }
void set_error(error_code ec, diagnostics&& diag)
{
value.emplace<0>(errcode_with_diagnostics{ec, std::move(diag)});
}
} impl_;
friend struct detail::access;
#endif
bool has_error() const { return impl_.value.index() == 0u; }
BOOST_MYSQL_DECL
void check_has_results() const;
public:
/**
* \brief Default constructor.
* \details
* Constructs an object containing an empty error (a default-constructed \ref errcode_with_diagnostics).
*
* \par Exception safety
* No-throw guarantee.
*/
any_stage_response() = default;
/**
* \brief Returns true if the object contains a statement.
*
* \par Exception safety
* No-throw guarantee.
*/
bool has_statement() const noexcept { return impl_.value.index() == 1u; }
/**
* \brief Returns true if the object contains a results.
*
* \par Exception safety
* No-throw guarantee.
*/
bool has_results() const noexcept { return impl_.value.index() == 2u; }
/**
* \brief Retrieves the contained error (const lvalue reference accessor).
* \details
* If `*this` contains an error, retrieves it by copying it.
* Otherwise (if `this->has_statement() || this->has_results()`),
* returns an empty (default-constructed) error.
*
* \par Exception safety
* Strong guarantee. Memory allocations when copying the error's diagnostics may throw.
*/
errcode_with_diagnostics error() const& noexcept
{
return has_error() ? variant2::unsafe_get<0>(impl_.value) : errcode_with_diagnostics();
}
/**
* \brief Retrieves the contained error (rvalue reference accessor).
* \details
* If `*this` contains an error, retrieves it by moving it.
* Otherwise (if `this->has_statement() || this->has_results()`),
* returns an empty (default-constructed) error.
*
* \par Exception safety
* No-throw guarantee.
*/
errcode_with_diagnostics error() && noexcept
{
return has_error() ? variant2::unsafe_get<0>(std::move(impl_.value)) : errcode_with_diagnostics();
}
/**
* \brief Retrieves the contained statement or throws an exception.
* \details
* If `*this` contains an statement (`this->has_statement() == true`),
* retrieves it. Otherwise, throws an exception.
*
* \par Exception safety
* Strong guarantee.
* \throws std::invalid_argument If `*this` does not contain a statement.
*/
BOOST_MYSQL_DECL
statement as_statement() const;
/**
* \brief Retrieves the contained statement (unchecked accessor).
* \details
* If `*this` contains an statement,
* retrieves it. Otherwise, the behavior is undefined.
*
* \par Preconditions
* `this->has_statement() == true`
*
* \par Exception safety
* No-throw guarantee.
*/
statement get_statement() const noexcept
{
BOOST_ASSERT(has_statement());
return variant2::unsafe_get<1>(impl_.value);
}
/**
* \brief Retrieves the contained results or throws an exception.
* \details
* If `*this` contains a `results` object (`this->has_results() == true`),
* retrieves a reference to it. Otherwise, throws an exception.
*
* \par Exception safety
* Strong guarantee. Throws on invalid input.
* \throws std::invalid_argument If `this->has_results() == false`
*
* \par Object lifetimes
* The returned reference is valid as long as `*this` is alive
* and hasn't been assigned to.
*/
const results& as_results() const&
{
check_has_results();
return variant2::unsafe_get<2>(impl_.value);
}
/// \copydoc as_results
results&& as_results() &&
{
check_has_results();
return variant2::unsafe_get<2>(std::move(impl_.value));
}
/**
* \brief Retrieves the contained results (unchecked accessor).
* \details
* If `*this` contains a `results` object, retrieves a reference to it.
* Otherwise, the behavior is undefined.
*
* \par Preconditions
* `this->has_results() == true`
*
* \par Exception safety
* No-throw guarantee.
*
* \par Object lifetimes
* The returned reference is valid as long as `*this` is alive
* and hasn't been assigned to.
*/
const results& get_results() const& noexcept
{
BOOST_ASSERT(has_results());
return variant2::unsafe_get<2>(impl_.value);
}
/// \copydoc get_results
results&& get_results() && noexcept
{
BOOST_ASSERT(has_results());
return variant2::unsafe_get<2>(std::move(impl_.value));
}
};
/**
* \brief (EXPERIMENTAL) A dynamic pipeline request.
* \details
* Contains a collection of pipeline stages, fully describing the work to be performed
* by a pipeline operation. The number of stages and their type is determined at runtime.
* Call any of the `add_xxx` functions to append new stages to the request.
* \n
* If the number of stages and their type is known at compile time, prefer using
* \ref static_pipeline_request, instead.
* \n
* Stage responses are read into a vector of \ref any_stage_response, which is variant-like.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
class pipeline_request
{
#ifndef BOOST_MYSQL_DOXYGEN
struct impl_t
{
std::vector<std::uint8_t> buffer_;
std::vector<detail::pipeline_request_stage> stages_;
detail::pipeline_request_view to_view() const { return {buffer_, stages_}; }
} impl_;
friend struct detail::access;
#endif
public:
/**
* \brief Default constructor.
* \details Constructs an empty pipeline request, with no stages.
*
* \par Exception safety
* No-throw guarantee.
*/
pipeline_request() = default;
/**
* \brief Removes all stages in the pipeline request, making the object empty again.
* \details
* Can be used to re-use a single request object for multiple pipeline operations.
*
* \par Exception safety
* No-throw guarantee.
*/
void clear() noexcept
{
impl_.buffer_.clear();
impl_.stages_.clear();
}
/**
* \brief Adds a stage to the pipeline request.
* \details
* Adds a stage with effects depending on the passed type. `PipelineStageType`
* should be one of: \n
* \li \ref execute_stage
* \li \ref prepare_statement_stage
* \li \ref close_statement_stage
* \li \ref reset_connection_stage
* \li \ref set_character_set_stage
*
* \par Exception safety
* Basic guarantee. Memory allocations while composing the request may throw.
* Throws if `stage` contains invalid parameters. Consult individual stage descriptions for details.
* \throws std::invalid_argument If `stage` contains invalid parameters.
*
* \par Object lifetimes
* All arguments are copied into the request buffer. Once this function returns,
* `stage` and any values that `stage` points to need not be kept alive.
*/
template <BOOST_MYSQL_PIPELINE_STAGE_TYPE PipelineStageType>
pipeline_request& add(PipelineStageType stage)
{
static_assert(detail::is_pipeline_stage_type<PipelineStageType>::value, "Invalid PipelineStageType");
impl_.stages_.push_back(detail::pipeline_stage_access::create(stage, impl_.buffer_));
return *this;
}
/**
* \brief The response type to use when running a pipeline with this request type, consisting of \ref
* any_stage_response objects.
*/
using response_type = std::vector<any_stage_response>;
};
/**
* \brief (EXPERIMENTAL) A static pipeline request type.
* \details
* Contains a collection of pipeline stages, fully describing the work to be performed
* by a pipeline operation. The pipeline contains as many stages as `PipelineStageType`
* template parameters are passed to this type.
* \n
* The following stage types may be used: \n
* \li \ref execute_stage
* \li \ref prepare_statement_stage
* \li \ref close_statement_stage
* \li \ref reset_connection_stage
* \li \ref set_character_set_stage
* \n
* Stage responses are read into `std::tuple` objects, with individual elements depending
* on stage types. See \ref response_type for more info.
* \n
* To create instances of this class without specifying template parameters,
* you can use \ref make_pipeline_request. If you're on C++17 or above, appropriate class deduction
* guides are also provided.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
template <BOOST_MYSQL_PIPELINE_STAGE_TYPE... PipelineStageType>
class static_pipeline_request
{
static_assert(sizeof...(PipelineStageType) > 0u, "A pipeline should have one stage, at least");
static_assert(
mp11::mp_all_of<mp11::mp_list<PipelineStageType...>, detail::is_pipeline_stage_type>::value,
"Some pipeline stage types are not valid"
);
struct impl_t
{
std::vector<std::uint8_t> buffer_;
std::array<detail::pipeline_request_stage, sizeof...(PipelineStageType)> stages_;
detail::pipeline_request_view to_view() const { return {buffer_, stages_}; }
impl_t(const PipelineStageType&... args)
: stages_{{detail::pipeline_stage_access::create(args, buffer_)...}}
{
}
} impl_;
#ifndef BOOST_MYSQL_DOXYGEN
friend struct detail::access;
#endif
public:
/**
* \brief Constructor.
* \details
* Takes as many arguments as stages the pipeline has. Stage types include
* all the parameters required to fully compose the request.
*
* \par Exception safety
* Strong guarantee. Memory allocations while composing the request may throw.
* Supplying steps with invalid arguments also throws.
* See individual stage descriptions for possible error conditions.
* \throws std::invalid_argument If any of `stages` contain invalid parameters.
*
* \par Object lifetimes
* Parameters are stored as a serialized request on the constructed object.
* The `stages` objects, and any other objects they point to, need not be kept alive
* once the constructor returns.
*/
static_pipeline_request(const PipelineStageType&... stages) : impl_(stages...) {}
/**
* \brief The response type to use when running a pipeline with this request type.
* \details
* Has the same number of elements as pipeline stages. Consult each stage
* `response_type` typedef for more info.
*/
using response_type = std::tuple<typename PipelineStageType::response_type...>;
};
/**
* \brief (EXPERIMENTAL) Creates a static_pipeline_request object.
* \details
* This factory function can be used to avoid having to explicitly specify
* template parameters on the request type. For C++17 code and later,
* you can directly use \ref static_pipeline_request constructor, instead.
*
* \par Exception safety
* Strong guarantee. Memory allocations while composing the request may throw.
* Supplying steps with invalid arguments also throws.
* \throws std::invalid_argument If `stages` contains a stage with invalid parameters.
* See individual stage descriptions for possible error conditions.
*
* \par Object lifetimes
* The `stages` objects, and any other objects they point to, need not be kept alive
* once the constructor returns.
*
* \par Experimental
* This part of the API is experimental, and may change in successive
* releases without previous notice.
*/
template <BOOST_MYSQL_PIPELINE_STAGE_TYPE... PipelineStageType>
static_pipeline_request<PipelineStageType...> make_pipeline_request(const PipelineStageType&... stages)
{
return {stages...};
}
#ifndef BOOST_NO_CXX17_DEDUCTION_GUIDES
template <BOOST_MYSQL_PIPELINE_STAGE_TYPE... PipelineStageType>
static_pipeline_request(const PipelineStageType&... args) -> static_pipeline_request<PipelineStageType...>;
#endif
} // namespace mysql
} // namespace boost
#include <boost/mysql/impl/pipeline.hpp>
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/pipeline.ipp>
#endif
#endif
+2
View File
@@ -28,6 +28,7 @@
#include <boost/mysql/impl/datetime.ipp>
#include <boost/mysql/impl/engine_impl_instantiations.ipp>
#include <boost/mysql/impl/error_categories.ipp>
#include <boost/mysql/impl/error_with_diagnostics.ipp>
#include <boost/mysql/impl/escape_string.ipp>
#include <boost/mysql/impl/execution_state_impl.ipp>
#include <boost/mysql/impl/field.ipp>
@@ -38,6 +39,7 @@
#include <boost/mysql/impl/internal/error/server_error_to_string.ipp>
#include <boost/mysql/impl/is_fatal_error.ipp>
#include <boost/mysql/impl/meta_check_context.ipp>
#include <boost/mysql/impl/pipeline.ipp>
#include <boost/mysql/impl/results_impl.ipp>
#include <boost/mysql/impl/resultset.ipp>
#include <boost/mysql/impl/row_impl.ipp>
+1
View File
@@ -29,6 +29,7 @@ target_sources(
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/common/src/entry_point.cpp
${CMAKE_CURRENT_SOURCE_DIR}/common/src/tracker_executor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/common/src/printing.cpp
)
target_link_libraries(
boost_mysql_testing
+1
View File
@@ -113,6 +113,7 @@ alias common_test_sources
:
common/src/entry_point.cpp
common/src/tracker_executor.cpp
common/src/printing.cpp
;
# Boost.Context causes failures with warnings-as-errors
+23 -59
View File
@@ -8,77 +8,41 @@
#ifndef BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_PRINTING_HPP
#define BOOST_MYSQL_TEST_COMMON_INCLUDE_TEST_COMMON_PRINTING_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/test/unit_test.hpp>
#include <ostream>
#include <iosfwd>
namespace boost {
namespace mysql {
inline std::ostream& operator<<(std::ostream& os, client_errc v)
{
return os << get_client_category().message(static_cast<int>(v));
}
enum class client_errc;
std::ostream& operator<<(std::ostream& os, client_errc v);
inline std::ostream& operator<<(std::ostream& os, common_server_errc v)
{
return os << get_common_server_category().message(static_cast<int>(v));
}
enum class common_server_errc;
std::ostream& operator<<(std::ostream& os, common_server_errc v);
inline std::ostream& operator<<(std::ostream& os, const diagnostics& diag)
{
return os << diag.server_message();
}
class diagnostics;
std::ostream& operator<<(std::ostream& os, const diagnostics& v);
inline std::ostream& operator<<(std::ostream& os, const row_view& value)
{
os << '{';
if (!value.empty())
{
os << value[0];
for (auto it = std::next(value.begin()); it != value.end(); ++it)
{
os << ", " << *it;
}
}
return os << '}';
}
class row_view;
std::ostream& operator<<(std::ostream& os, const row_view& v);
inline std::ostream& operator<<(std::ostream& os, const row& r) { return os << row_view(r); }
class row;
std::ostream& operator<<(std::ostream& os, const row& v);
inline std::ostream& operator<<(std::ostream& os, metadata_mode v)
{
switch (v)
{
case metadata_mode::full: return os << "full";
case metadata_mode::minimal: return os << "minimal";
default: return os << "<unknown metadata_mode>";
}
}
enum class metadata_mode;
std::ostream& operator<<(std::ostream& os, metadata_mode v);
inline std::ostream& operator<<(std::ostream& os, ssl_mode v)
{
switch (v)
{
case ssl_mode::disable: return os << "disable";
case ssl_mode::enable: return os << "enable";
case ssl_mode::require: return os << "require";
default: return os << "<unknown ssl_mode>";
}
}
enum class ssl_mode;
std::ostream& operator<<(std::ostream& os, ssl_mode v);
struct character_set;
bool operator==(const character_set& lhs, const character_set& rhs);
std::ostream& operator<<(std::ostream& os, const character_set& v);
struct errcode_with_diagnostics;
bool operator==(const errcode_with_diagnostics& lhs, const errcode_with_diagnostics& rhs);
std::ostream& operator<<(std::ostream& os, const errcode_with_diagnostics& v);
} // namespace mysql
} // namespace boost
BOOST_TEST_DONT_PRINT_LOG_VALUE(boost::mysql::time)
#endif
+101
View File
@@ -0,0 +1,101 @@
//
// 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/character_set.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/error_with_diagnostics.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/access.hpp>
#include <ostream>
#include "test_common/printing.hpp"
using namespace boost::mysql;
std::ostream& boost::mysql::operator<<(std::ostream& os, client_errc v) { return os << error_code(v); }
std::ostream& boost::mysql::operator<<(std::ostream& os, common_server_errc v) { return os << error_code(v); }
std::ostream& boost::mysql::operator<<(std::ostream& os, const diagnostics& diag)
{
const auto& impl = detail::access::get_impl(diag);
return os << "diagnostics{ " << (impl.is_server ? ".server_message" : ".client_message") << " = \""
<< impl.msg << "\" }";
}
std::ostream& boost::mysql::operator<<(std::ostream& os, const row_view& value)
{
os << '{';
if (!value.empty())
{
os << value[0];
for (auto it = std::next(value.begin()); it != value.end(); ++it)
{
os << ", " << *it;
}
}
return os << '}';
}
std::ostream& boost::mysql::operator<<(std::ostream& os, const row& r) { return os << row_view(r); }
static const char* to_string(metadata_mode v)
{
switch (v)
{
case metadata_mode::full: return "metadata_mode::full";
case metadata_mode::minimal: return "metadata_mode::minimal";
default: return "<unknown metadata_mode>";
}
}
std::ostream& boost::mysql::operator<<(std::ostream& os, metadata_mode v) { return os << ::to_string(v); }
static const char* to_string(ssl_mode v)
{
switch (v)
{
case ssl_mode::disable: return "ssl_mode::disable";
case ssl_mode::enable: return "ssl_mode::enable";
case ssl_mode::require: return "ssl_mode::require";
default: return "<unknown ssl_mode>";
}
}
std::ostream& boost::mysql::operator<<(std::ostream& os, ssl_mode v) { return os << ::to_string(v); }
// character set
bool boost::mysql::operator==(const character_set& lhs, const character_set& rhs)
{
return lhs.name == rhs.name;
}
std::ostream& boost::mysql::operator<<(std::ostream& os, const character_set& v)
{
return os << "character_set(\"" << v.name << "\")";
}
// errcode_with_diagnostics
bool boost::mysql::operator==(const errcode_with_diagnostics& lhs, const errcode_with_diagnostics& rhs)
{
return lhs.code == rhs.code && lhs.diag == rhs.diag;
}
std::ostream& boost::mysql::operator<<(std::ostream& os, const errcode_with_diagnostics& v)
{
return os << "errcode_with_diagnostics{ .code = " << v.code << ", .diag = " << v.diag << " }";
}
+2
View File
@@ -29,6 +29,7 @@ add_executable(
test/reconnect.cpp
test/any_connection.cpp
test/character_set_tracking.cpp
test/pipeline.cpp
test/connection_pool.cpp
test/db_specific.cpp
test/database_types.cpp
@@ -46,6 +47,7 @@ add_executable(
test/snippets/any_connection.cpp
test/snippets/connection_pool.cpp
test/snippets/sql_formatting.cpp
test/snippets/pipeline.cpp
)
target_include_directories(
boost_mysql_integrationtests
+2
View File
@@ -59,6 +59,7 @@ run
test/reconnect.cpp
test/any_connection.cpp
test/character_set_tracking.cpp
test/pipeline.cpp
test/connection_pool.cpp
test/db_specific.cpp
test/database_types.cpp
@@ -76,6 +77,7 @@ run
test/snippets/any_connection.cpp
test/snippets/connection_pool.cpp
test/snippets/sql_formatting.cpp
test/snippets/pipeline.cpp
: requirements
<testing.arg>$(test_command)
@@ -0,0 +1,53 @@
//
// 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_SNIPPETS_GET_ANY_CONNECTION_HPP
#define BOOST_MYSQL_TEST_INTEGRATION_INCLUDE_TEST_INTEGRATION_SNIPPETS_GET_ANY_CONNECTION_HPP
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/connect_params.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/asio/io_context.hpp>
#include "test_common/ci_server.hpp"
#include "test_integration/snippets/credentials.hpp"
namespace boost {
namespace mysql {
namespace test {
struct any_connection_and_context
{
asio::io_context ctx;
any_connection conn{ctx.get_executor()};
any_connection_and_context()
{
// Connect
boost::mysql::connect_params params;
params.server_address.emplace_host_and_port(get_hostname());
params.username = mysql_username;
params.password = mysql_password;
params.database = "boost_mysql_examples";
params.ssl = ssl_mode::disable;
params.multi_queries = true;
conn.connect(params);
}
};
inline any_connection& get_any_connection()
{
static any_connection_and_context obj;
return obj.conn;
}
} // namespace test
} // namespace mysql
} // namespace boost
#endif
@@ -7,6 +7,7 @@
#include <boost/mysql/any_connection.hpp>
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/mysql_collations.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/string_view.hpp>
+228
View File
@@ -0,0 +1,228 @@
//
// 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_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 "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"
using namespace boost::mysql::test;
using namespace boost::mysql;
namespace asio = boost::asio;
using boost::test_tools::per_element;
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(dynamic_success, fixture)
{
// Setup
pipeline_request req;
pipeline_request::response_type res;
// Populate the request
req.add(reset_connection_stage())
.add(execute_stage("SET @myvar = 15"))
.add(prepare_statement_stage("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();
// Check results
BOOST_TEST_REQUIRE(res.size() == 3u);
BOOST_TEST(res[0].error() == errcode_with_diagnostics{});
BOOST_TEST(!res[0].has_results());
BOOST_TEST(!res[0].has_statement());
BOOST_TEST(res[1].has_results());
auto stmt = res[2].as_statement();
BOOST_TEST(stmt.valid());
BOOST_TEST(conn.current_character_set().error() == client_errc::unknown_character_set);
// Re-populate the pipeline, with different stages
req.clear();
req.add(set_character_set_stage(utf8mb4_charset))
.add(execute_stage(stmt, {0}))
.add(execute_stage(stmt, {1}))
.add(close_statement_stage(stmt));
// Run it
result = create_initial_netresult<void>();
conn.run_pipeline(req, res, result.err, *result.diag);
result.validate_no_error();
// Check results
BOOST_TEST_REQUIRE(res.size() == 4u);
BOOST_TEST(res[0].error() == errcode_with_diagnostics{});
BOOST_TEST(!res[0].has_results());
BOOST_TEST(!res[0].has_statement());
BOOST_TEST(res[1].as_results().rows() == rows(), per_element());
BOOST_TEST(res[2].as_results().rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(res[3].error() == errcode_with_diagnostics{});
BOOST_TEST(!res[3].has_results());
BOOST_TEST(!res[3].has_statement());
}
BOOST_FIXTURE_TEST_CASE(dynamic_errors, fixture)
{
// Setup
pipeline_request req;
pipeline_request::response_type res;
// Populate the request with some successes and some errors
req.add(execute_stage("SET @myvar = 42")) // OK
.add(prepare_statement_stage("SELECT * FROM bad_table WHERE id = ?")) // error: bad table
.add(execute_stage("")) // error: empty query
.add(execute_stage("SELECT @myvar")) // OK
.add(set_character_set_stage(character_set{"bad_charset", utf8mb4_charset.next_char})) // bad charset
.add(execute_stage("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"
);
// Check results
BOOST_TEST_REQUIRE(res.size() == 6u);
BOOST_TEST(res[0].has_results());
BOOST_TEST(
res[1].error() == (errcode_with_diagnostics{
common_server_errc::er_no_such_table,
create_server_diag("Table 'boost_mysql_integtests.bad_table' doesn't exist")
})
);
BOOST_TEST(
res[2].error() ==
(errcode_with_diagnostics{common_server_errc::er_empty_query, create_server_diag("Query was empty")})
);
BOOST_TEST(res[3].as_results().rows() == makerows(1, 42), per_element());
BOOST_TEST(
res[4].error() == (errcode_with_diagnostics{
common_server_errc::er_unknown_character_set,
create_server_diag("Unknown character set: 'bad_charset'")
})
);
BOOST_TEST(res[5].as_results().rows() == makerows(1, "abc"), per_element());
}
BOOST_FIXTURE_TEST_CASE(static_success, fixture)
{
// Setup
auto req = make_pipeline_request(
reset_connection_stage(),
execute_stage("SET @myvar = 15"),
prepare_statement_stage("SELECT * FROM one_row_table WHERE id = ?")
);
decltype(req)::response_type res;
// Run it
auto result = create_initial_netresult<void>();
conn.run_pipeline(req, res, result.err, *result.diag);
result.validate_no_error();
// Check results
BOOST_TEST(std::get<0>(res) == errcode_with_diagnostics{});
BOOST_TEST(std::get<1>(res).has_value());
auto stmt = std::get<2>(res).value();
BOOST_TEST(stmt.valid());
BOOST_TEST(conn.current_character_set().error() == client_errc::unknown_character_set);
// Create another pipeline
auto req2 = make_pipeline_request(
set_character_set_stage(utf8mb4_charset),
execute_stage(stmt, {0}),
execute_stage(stmt, {1}),
close_statement_stage(stmt)
);
decltype(req2)::response_type res2;
// Run it
result = create_initial_netresult<void>();
conn.run_pipeline(req2, res2, result.err, *result.diag);
result.validate_no_error();
// Check results
BOOST_TEST(std::get<0>(res2) == errcode_with_diagnostics{});
BOOST_TEST(std::get<1>(res2).value().rows() == rows(), per_element());
BOOST_TEST(std::get<2>(res2).value().rows() == makerows(2, 1, "f0"), per_element());
BOOST_TEST(std::get<3>(res2) == errcode_with_diagnostics{});
}
BOOST_FIXTURE_TEST_CASE(static_errors, fixture)
{
// Setup
auto req = make_pipeline_request(
execute_stage("SET @myvar = 42"), // OK
prepare_statement_stage("SELECT * FROM bad_table WHERE id = ?"), // error: bad table
execute_stage(""), // error: empty query
execute_stage("SELECT @myvar"), // OK
set_character_set_stage(character_set{"bad_charset", utf8mb4_charset.next_char}), // bad charset
execute_stage("SELECT 'abc'") // OK
);
decltype(req)::response_type res;
// 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"
);
// Check results
BOOST_TEST(std::get<0>(res).has_value());
BOOST_TEST(
std::get<1>(res).error() ==
(errcode_with_diagnostics{
common_server_errc::er_no_such_table,
create_server_diag("Table 'boost_mysql_integtests.bad_table' doesn't exist")
})
);
BOOST_TEST(
std::get<2>(res).error() ==
(errcode_with_diagnostics{common_server_errc::er_empty_query, create_server_diag("Query was empty")})
);
BOOST_TEST(std::get<3>(res).value().rows() == makerows(1, 42), per_element());
BOOST_TEST(
std::get<4>(res) == (errcode_with_diagnostics{
common_server_errc::er_unknown_character_set,
create_server_diag("Unknown character set: 'bad_charset'")
})
);
BOOST_TEST(std::get<5>(res).value().rows() == makerows(1, "abc"), per_element());
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace
@@ -5,6 +5,7 @@
// 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/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/execution_state.hpp>
@@ -17,12 +18,9 @@
#include <boost/test/unit_test.hpp>
#include <tuple>
#include "test_common/create_basic.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/er_connection.hpp"
#include "test_integration/tcp_network_fixture.hpp"
using namespace boost::mysql::test;
+308
View File
@@ -0,0 +1,308 @@
//
// 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_connection.hpp>
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/system/system_error.hpp>
#include <boost/test/unit_test.hpp>
#include <vector>
#include "test_common/create_basic.hpp"
#include "test_common/printing.hpp"
#include "test_integration/snippets/get_any_connection.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::test_tools::per_element;
namespace {
BOOST_AUTO_TEST_CASE(section_pipeline_dynamic)
{
auto& conn = get_any_connection();
//[pipeline_dynamic_request
// Create a pipeline request and add three stages to it.
// When run, this pipeline will set the connection's character set to utf8mb4
// and prepare two statements.
pipeline_request req;
req.add(set_character_set_stage(utf8mb4_charset))
.add(prepare_statement_stage("INSERT INTO audit_log (t, msg) VALUES (?, ?)"))
.add(prepare_statement_stage(
"INSERT INTO employee (company_id, first_name, last_name) VALUES (?, ?, ?)"
));
//]
//[pipeline_dynamic_run
// Run the pipeline request req, and store responses into res
// any_stage_response is a variant-like type that can store the response
// of any stage type (including results and statements).
std::vector<any_stage_response> res;
conn.run_pipeline(req, res);
//]
//[pipeline_dynamic_results
// The 2nd and 3rd stages were statement preparation requests,
// so res[1] and res[2] contain statement objects
statement stmt1 = res[1].as_statement();
statement stmt2 = res[2].as_statement();
//]
BOOST_TEST(stmt1.valid());
BOOST_TEST(stmt2.valid());
}
BOOST_AUTO_TEST_CASE(section_pipeline_static)
{
auto& conn = get_any_connection();
//[pipeline_static
// Create a pipeline request containing three stages.
// When run, this pipeline will set the connection's character set to utf8mb4
// and prepare two statements.
// req is a static_pipeline_request<set_character_set_stage, prepare_statement_stage,
// prepare_statement_stage>
auto req = make_pipeline_request(
set_character_set_stage(utf8mb4_charset),
prepare_statement_stage("INSERT INTO audit_log (t, msg) VALUES (?, ?)"),
prepare_statement_stage("INSERT INTO employee (company_id, first_name, last_name) VALUES (?, ?, ?)")
);
// Create a response object. Instead of vectors, static pipelines use tuples.
// Each stage type has an associated response type
// res is a std::tuple<set_character_set_stage::response_type,
// prepare_statement_stage::response_type, prepare_statement_stage::response_type>
decltype(req)::response_type res;
// Run the pipeline
conn.run_pipeline(req, res);
// Access your statements
statement stmt1 = std::get<1>(res).value();
statement stmt2 = std::get<2>(res).value();
//]
BOOST_TEST(stmt1.valid());
BOOST_TEST(stmt2.valid());
}
BOOST_AUTO_TEST_CASE(section_pipeline_errors)
{
auto& conn = get_any_connection();
//[pipeline_errors
// The second step in the pipeline will fail, the other ones will succeeded
pipeline_request req;
req.add(set_character_set_stage(utf8mb4_charset))
.add(prepare_statement_stage("INSERT INTO bad_table (t, msg) VALUES (?, ?)")) // will fail
.add(prepare_statement_stage(
"INSERT INTO employee (company_id, first_name, last_name) VALUES (?, ?, ?)"
));
std::vector<any_stage_response> res;
error_code ec;
diagnostics diag;
conn.run_pipeline(req, res, ec, diag);
BOOST_TEST(ec == common_server_errc::er_no_such_table);
BOOST_TEST(res[0].error().code == error_code());
BOOST_TEST(res[1].error().code == common_server_errc::er_no_such_table);
BOOST_TEST(res[2].error().code == error_code());
//]
}
BOOST_AUTO_TEST_CASE(section_pipeline_pitfalls)
{
auto& conn = get_any_connection();
{
//[pipeline_pitfalls_bad
// This doesn't behave correctly - DO NOT DO THIS
// The first INSERT will fail due to a failed foreign key check (there is no such company),
// but COMMIT will still be run, thus leaving us with an inconsistent data model
pipeline_request req;
req.add(execute_stage("START TRANSACTION"))
.add(execute_stage(
"INSERT INTO employee (first_name, last_name, company_id) VALUES ('John', 'Doe', 'bad')"
))
.add(execute_stage("INSERT INTO logs VALUES ('Inserted 1 employee')"))
.add(execute_stage("COMMIT"));
//]
decltype(req)::response_type res;
error_code ec;
diagnostics diag;
conn.run_pipeline(req, res, ec, diag);
BOOST_TEST(ec == common_server_errc::er_no_referenced_row_2);
}
{
try
{
//[pipeline_pitfalls_good
const char* sql =
"START TRANSACTION;"
"INSERT INTO employee (first_name, last_name, company_id) VALUES ('John', 'Doe', 'bad');"
"INSERT INTO logs VALUES ('Inserted 1 employee');"
"COMMIT";
// After the first INSERT fails, nothing else will be run. This is what we want.
// Note that you need to enable multi queries when connecting to be able to run this.
results r;
conn.execute(sql, r);
//]
}
catch (const boost::system::system_error& err)
{
BOOST_TEST(err.code() == common_server_errc::er_no_referenced_row_2);
}
}
}
BOOST_AUTO_TEST_CASE(section_pipeline_reference)
{
auto& conn = get_any_connection();
results result;
std::vector<any_stage_response> pipe_res;
// Execute
{
pipeline_request req;
auto stmt = conn.prepare_statement("SELECT ?, ?, ?");
//[pipeline_reference_execute
// Text query
req.add(execute_stage("SELECT 1"));
// Prepared statement, with number of parameters known at compile time
req.add(execute_stage(stmt, {"John", "Doe", 42}));
// Prepared statement, with number of parameters unknown at compile time
std::vector<field_view> params{
/* ... */
//<-
field_view("Janet"),
field_view("Joyce"),
field_view(50)
//->
};
req.add(execute_stage(stmt, params));
//]
conn.run_pipeline(req, pipe_res);
BOOST_TEST(pipe_res.at(0).as_results().rows() == makerows(1, 1), per_element());
BOOST_TEST(pipe_res.at(1).as_results().rows() == makerows(3, "John", "Doe", 42), per_element());
BOOST_TEST(pipe_res.at(2).as_results().rows() == makerows(3, "Janet", "Joyce", 50), per_element());
}
{
auto stmt = conn.prepare_statement("SELECT ?, ?, ?");
//[pipeline_reference_execute_equivalent
// Text query
conn.execute("SELECT 1", result);
//<-
BOOST_TEST(result.rows() == makerows(1, 1), per_element());
//->
// Prepared statement, with number of parameters known at compile time
conn.execute(stmt.bind("John", "Doe", 42), result);
//<-
BOOST_TEST(result.rows() == makerows(3, "John", "Doe", 42), per_element());
//->
// Prepared statement, with number of parameters unknown at compile time
std::vector<field_view> params{
/* ... */
//<-
field_view("Janet"),
field_view("Joyce"),
field_view(50)
//->
};
conn.execute(stmt.bind(params.begin(), params.end()), result);
//]
BOOST_TEST(result.rows() == makerows(3, "Janet", "Joyce", 50), per_element());
}
// Prepare statement
{
pipeline_request req;
//[pipeline_reference_prepare_statement
req.add(prepare_statement_stage("SELECT * FROM employee WHERE id = ?"));
//]
conn.run_pipeline(req, pipe_res);
BOOST_TEST(pipe_res.at(0).as_statement().valid());
}
{
//[pipeline_reference_prepare_statement_equivalent
statement stmt = conn.prepare_statement("SELECT * FROM employee WHERE id = ?");
//]
BOOST_TEST(stmt.valid());
}
// Close statement
{
pipeline_request req;
auto stmt = conn.prepare_statement("SELECT 1");
//[pipeline_reference_close_statement
req.add(close_statement_stage(stmt));
//]
conn.run_pipeline(req, pipe_res);
}
{
auto stmt = conn.prepare_statement("SELECT 1");
//[pipeline_reference_close_statement_equivalent
conn.close_statement(stmt);
//]
}
// Reset connection
{
pipeline_request req;
//[pipeline_reference_reset_connection
req.add(reset_connection_stage());
//]
}
{
//[pipeline_reference_reset_connection_equivalent
conn.reset_connection();
//]
}
// Set character set
{
pipeline_request req;
//[pipeline_reference_set_character_set
req.add(set_character_set_stage(utf8mb4_charset));
//]
}
{
//[pipeline_reference_set_character_set_equivalent
conn.set_character_set(utf8mb4_charset);
//]
}
}
} // namespace
@@ -25,9 +25,8 @@
#include <string>
#include <utility>
#include "test_common/ci_server.hpp"
#include "test_common/printing.hpp"
#include "test_integration/snippets/credentials.hpp"
#include "test_integration/snippets/get_any_connection.hpp"
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
#include <optional>
@@ -157,22 +156,9 @@ void test_compose_update_query()
BOOST_AUTO_TEST_CASE(section_sql_formatting)
{
auto server_hostname = get_hostname();
connect_params params;
params.server_address.emplace_host_and_port(server_hostname);
params.username = mysql_username;
params.password = mysql_password;
params.database = "boost_mysql_examples";
params.multi_queries = true;
params.ssl = ssl_mode::disable;
boost::asio::io_context ioc;
any_connection conn(ioc);
auto& conn = get_any_connection();
results r;
conn.connect(params);
{
//[sql_formatting_simple
std::string employee_name = get_name(); // employee_name is an untrusted string
+91 -2
View File
@@ -10,8 +10,10 @@
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows_view.hpp>
@@ -20,7 +22,9 @@
#include <boost/mysql/detail/config.hpp>
#include "test_common/create_basic.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_common/netfun_maker.hpp"
#include "test_common/printing.hpp"
#include "test_integration/common.hpp"
#include "test_integration/er_connection.hpp"
#include "test_integration/network_test.hpp"
@@ -496,7 +500,7 @@ struct
},
};
BOOST_AUTO_TEST_CASE(spotcheck_success)
BOOST_AUTO_TEST_CASE(set_character_set_success)
{
for (const auto& fns : set_charset_all_fns)
{
@@ -516,7 +520,7 @@ BOOST_AUTO_TEST_CASE(spotcheck_success)
}
}
BOOST_AUTO_TEST_CASE(spotcheck_error)
BOOST_AUTO_TEST_CASE(set_character_set_error)
{
for (const auto& fns : set_charset_all_fns)
{
@@ -540,4 +544,89 @@ BOOST_AUTO_TEST_CASE(spotcheck_error)
}
}
// same for run_pipeline
using run_pipeline_netmaker = netfun_maker_mem<
void,
any_connection,
const pipeline_request&,
pipeline_request::response_type&>;
struct
{
string_view name;
run_pipeline_netmaker::signature run_pipeline;
} run_pipeline_all_fns[] = {
{"sync_errc", run_pipeline_netmaker::sync_errc(&any_connection::run_pipeline)},
{"sync_exc", run_pipeline_netmaker::sync_exc(&any_connection::run_pipeline)},
{"async_errinfo", run_pipeline_netmaker::async_errinfo(&any_connection::async_run_pipeline, false)},
{"async_noerrinfo", run_pipeline_netmaker::async_noerrinfo(&any_connection::async_run_pipeline, false)},
};
BOOST_AUTO_TEST_CASE(run_pipeline_success)
{
for (const auto& fns : run_pipeline_all_fns)
{
BOOST_TEST_CONTEXT(fns.name)
{
// Setup
boost::asio::io_context ctx;
any_connection conn(ctx);
conn.connect(default_connect_params(ssl_mode::disable));
pipeline_request req;
req.add(set_character_set_stage(ascii_charset))
.add(execute_stage("SET @myvar = 42"))
.add(execute_stage("SELECT @myvar"));
pipeline_request::response_type res;
// Issue the pipeline
fns.run_pipeline(conn, req, res).validate_no_error();
// Success
BOOST_TEST(conn.current_character_set().value() == ascii_charset);
BOOST_TEST_REQUIRE(res.size() == 3u);
BOOST_TEST(res.at(0).error() == errcode_with_diagnostics());
BOOST_TEST(res.at(1).as_results().rows().empty());
BOOST_TEST(res.at(2).as_results().rows() == makerows(1, 42));
}
}
}
BOOST_AUTO_TEST_CASE(run_pipeline_error)
{
for (const auto& fns : run_pipeline_all_fns)
{
BOOST_TEST_CONTEXT(fns.name)
{
// Setup
boost::asio::io_context ctx;
any_connection conn(ctx);
conn.connect(default_connect_params(ssl_mode::disable));
pipeline_request req;
req.add(execute_stage("SET @myvar = 42"))
.add(prepare_statement_stage("SELECT * FROM bad_table"))
.add(execute_stage("SELECT @myvar"));
pipeline_request::response_type res;
// Issue the command
fns.run_pipeline(conn, req, res)
.validate_error_exact(
common_server_errc::er_no_such_table,
"Table 'boost_mysql_integtests.bad_table' doesn't exist"
);
// Stages 0 and 2 were executed successfully
BOOST_TEST(res.size() == 3u);
BOOST_TEST(res[0].as_results().rows().size() == 0u);
BOOST_TEST(
res[1].error() ==
(errcode_with_diagnostics{
common_server_errc::er_no_such_table,
create_server_diag("Table 'boost_mysql_integtests.bad_table' doesn't exist")
})
);
BOOST_TEST(res[2].as_results().rows() == makerows(1, 42));
}
}
}
BOOST_AUTO_TEST_SUITE_END() // test_spotchecks
+4 -1
View File
@@ -10,6 +10,7 @@ add_executable(
# Helpers
src/test_stream.cpp
src/printing.cpp
# Actual tests
test/auth/auth.cpp
@@ -28,7 +29,6 @@ add_executable(
test/protocol/deserialization.cpp
test/sansio/read_buffer.cpp
test/sansio/message_writer.cpp
test/sansio/message_reader.cpp
test/sansio/top_level_algo.cpp
@@ -41,6 +41,8 @@ add_executable(
test/sansio/set_character_set.cpp
test/sansio/ping.cpp
test/sansio/reset_connection.cpp
test/sansio/prepare_statement.cpp
test/sansio/run_pipeline.cpp
test/execution_processor/execution_processor.cpp
test/execution_processor/execution_state_impl.cpp
@@ -116,6 +118,7 @@ add_executable(
test/escape_string.cpp
test/constant_string_view.cpp
test/pfr.cpp
test/pipeline.cpp
)
target_include_directories(
boost_mysql_unittests
+4 -1
View File
@@ -19,6 +19,7 @@ run
# Helpers
src/test_stream.cpp
src/printing.cpp
# Actual tests
test/auth/auth.cpp
@@ -37,7 +38,6 @@ run
test/protocol/deserialization.cpp
test/sansio/read_buffer.cpp
test/sansio/message_writer.cpp
test/sansio/message_reader.cpp
test/sansio/top_level_algo.cpp
@@ -50,6 +50,8 @@ run
test/sansio/set_character_set.cpp
test/sansio/ping.cpp
test/sansio/reset_connection.cpp
test/sansio/prepare_statement.cpp
test/sansio/run_pipeline.cpp
test/execution_processor/execution_processor.cpp
test/execution_processor/execution_state_impl.cpp
@@ -125,6 +127,7 @@ run
test/escape_string.cpp
test/constant_string_view.cpp
test/pfr.cpp
test/pipeline.cpp
: requirements
<include>include
+56 -47
View File
@@ -14,10 +14,10 @@
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/asio/error.hpp>
#include <boost/config.hpp>
#include <boost/core/span.hpp>
#include <boost/test/unit_test.hpp>
#include <cassert>
@@ -38,41 +38,40 @@ namespace test {
class any_algo_ref
{
template <class Algo>
static detail::next_action do_resume(detail::sansio_algorithm* self, error_code ec)
static detail::next_action do_resume(void* self, detail::connection_state_data& st, error_code ec)
{
return static_cast<Algo*>(self)->resume(ec);
return static_cast<Algo*>(self)->resume(st, ec);
}
using fn_t = detail::next_action (*)(detail::sansio_algorithm*, error_code);
using fn_t = detail::next_action (*)(void*, detail::connection_state_data&, error_code);
detail::sansio_algorithm* algo_{};
void* algo_{};
fn_t fn_{};
public:
template <
class Algo,
class = typename std::enable_if<std::is_base_of<detail::sansio_algorithm, Algo>::value>::type>
any_algo_ref(Algo& op) noexcept : algo_(&op), fn_(&do_resume<Algo>)
template <class Algo, class = typename std::enable_if<!std::is_same<Algo, any_algo_ref>::value>::type>
any_algo_ref(Algo& algo) noexcept : algo_(&algo), fn_(&do_resume<Algo>)
{
}
detail::sansio_algorithm& get() noexcept { return *algo_; }
const detail::sansio_algorithm& get() const noexcept { return *algo_; }
detail::next_action resume(error_code ec) { return fn_(algo_, ec); }
detail::next_action resume(detail::connection_state_data& st, error_code ec)
{
return fn_(algo_, st, ec);
}
};
class BOOST_ATTRIBUTE_NODISCARD algo_test
{
struct step_t
{
detail::next_action::type_t type;
detail::next_action_type type;
std::vector<std::uint8_t> bytes;
error_code result;
};
std::vector<step_t> steps_;
void handle_read(const step_t& op, detail::connection_state_data& st)
static void handle_read(detail::connection_state_data& st, const step_t& op)
{
if (!op.result)
{
@@ -91,24 +90,27 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test
}
}
void handle_write(const step_t& op, detail::connection_state_data& st)
static void handle_write(span<const std::uint8_t> actual_msg, const step_t& op)
{
// Multi-frame messages are not supported by these tests (they don't add anything)
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st.writer.current_chunk(), op.bytes);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(actual_msg, op.bytes);
}
algo_test& add_step(detail::next_action::type_t act_type, std::vector<std::uint8_t> bytes, error_code ec)
algo_test& add_step(detail::next_action_type act_type, std::vector<std::uint8_t> bytes, error_code ec)
{
steps_.push_back(step_t{act_type, std::move(bytes), ec});
return *this;
}
detail::next_action run_algo_until_step(any_algo_ref algo, std::size_t num_steps_to_run)
detail::next_action run_algo_until_step(
detail::connection_state_data& st,
any_algo_ref algo,
std::size_t num_steps_to_run
) const
{
assert(num_steps_to_run <= num_steps());
// Start the op
auto act = algo.resume(error_code());
auto act = algo.resume(st, error_code());
// Go through the requested steps
for (std::size_t i = 0; i < num_steps_to_run; ++i)
@@ -117,44 +119,48 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test
{
const auto& step = steps_[i];
BOOST_TEST_REQUIRE(act.type() == step.type);
if (step.type == detail::next_action::type_t::read)
handle_read(step, algo.get().conn_state());
else if (step.type == detail::next_action::type_t::write)
handle_write(step, algo.get().conn_state());
if (step.type == detail::next_action_type::read)
handle_read(st, step);
else if (step.type == detail::next_action_type::write)
handle_write(act.write_args().buffer, step);
// Other actions don't need any handling
act = algo.resume(step.result);
act = algo.resume(st, step.result);
}
}
return act;
}
std::size_t num_steps() const noexcept { return steps_.size(); }
std::size_t num_steps() const { return steps_.size(); }
void check_impl(any_algo_ref algo, error_code expected_ec = {})
void check_impl(detail::connection_state_data& st, any_algo_ref algo, error_code expected_ec = {}) const
{
// Run the op until completion
auto act = run_algo_until_step(algo, steps_.size());
auto act = run_algo_until_step(st, algo, steps_.size());
// Check that we've finished
BOOST_TEST_REQUIRE(act.type() == detail::next_action::type_t::none);
BOOST_TEST_REQUIRE(act.type() == detail::next_action_type::none);
BOOST_TEST(act.error() == expected_ec);
}
void check_network_errors_impl(any_algo_ref algo, std::size_t step_number)
void check_network_errors_impl(
detail::connection_state_data& st,
any_algo_ref algo,
std::size_t step_number
) const
{
assert(step_number < num_steps());
// Run all the steps that shouldn't cause an error
auto act = run_algo_until_step(algo, step_number);
auto act = run_algo_until_step(st, algo, step_number);
BOOST_TEST_REQUIRE(act.type() == steps_[step_number].type);
// Trigger an error in the requested step
act = algo.resume(asio::error::bad_descriptor);
act = algo.resume(st, asio::error::bad_descriptor);
// The operation finished and returned the network error
BOOST_TEST_REQUIRE(act.type() == detail::next_action::type_t::none);
BOOST_TEST_REQUIRE(act.type() == detail::next_action_type::none);
BOOST_TEST(act.error() == error_code(asio::error::bad_descriptor));
}
@@ -164,55 +170,52 @@ public:
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_write(std::vector<std::uint8_t> bytes, error_code result = {})
{
return add_step(detail::next_action::type_t::write, std::move(bytes), result);
return add_step(detail::next_action_type::write, std::move(bytes), result);
}
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_read(std::vector<std::uint8_t> result_bytes)
{
return add_step(detail::next_action::type_t::read, std::move(result_bytes), error_code());
return add_step(detail::next_action_type::read, std::move(result_bytes), error_code());
}
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_read(error_code result)
{
return add_step(detail::next_action::type_t::read, {}, result);
}
algo_test& expect_read(error_code result) { return add_step(detail::next_action_type::read, {}, result); }
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_ssl_handshake(error_code result = {})
{
return add_step(detail::next_action::type_t::ssl_handshake, {}, result);
return add_step(detail::next_action_type::ssl_handshake, {}, result);
}
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_ssl_shutdown(error_code result = {})
{
return add_step(detail::next_action::type_t::ssl_shutdown, {}, result);
return add_step(detail::next_action_type::ssl_shutdown, {}, result);
}
BOOST_ATTRIBUTE_NODISCARD
algo_test& expect_close(error_code result = {})
{
return add_step(detail::next_action::type_t::close, {}, result);
return add_step(detail::next_action_type::close, {}, result);
}
template <class AlgoFixture>
void check(AlgoFixture& fix, error_code expected_ec = {}, const diagnostics& expected_diag = {})
void check(AlgoFixture& fix, error_code expected_ec = {}, const diagnostics& expected_diag = {}) const
{
check_impl(fix.algo, expected_ec);
check_impl(fix.st, fix.algo, expected_ec);
BOOST_TEST(fix.diag == expected_diag);
}
template <class AlgoFixture>
void check_network_errors()
void check_network_errors() const
{
for (std::size_t i = 0; i < num_steps(); ++i)
{
BOOST_TEST_CONTEXT("check_network_errors erroring at step " << i)
{
AlgoFixture fix;
check_network_errors_impl(fix.algo, i);
check_network_errors_impl(fix.st, fix.algo, i);
BOOST_TEST(fix.diag == diagnostics());
}
}
@@ -222,7 +225,13 @@ public:
struct algo_fixture_base
{
detail::connection_state_data st{512};
diagnostics diag{create_server_diag("Diagnostics not cleared")};
diagnostics diag;
algo_fixture_base(diagnostics initial_diag = create_server_diag("Diagnostics not cleared"))
: diag(std::move(initial_diag))
{
st.write_buffer.push_back(0xff); // Check that we clear the write buffer at each step
}
};
} // namespace test
@@ -0,0 +1,77 @@
//
// 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_CREATE_PREPARE_STATEMENT_RESPONSE_HPP
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_CREATE_PREPARE_STATEMENT_RESPONSE_HPP
#include <boost/mysql/impl/internal/protocol/impl/protocol_types.hpp>
#include <boost/mysql/impl/internal/protocol/impl/serialization_context.hpp>
#include <cstdint>
#include <vector>
#include "test_unit/create_frame.hpp"
namespace boost {
namespace mysql {
namespace test {
class prepare_stmt_response_builder
{
std::uint8_t seqnum_{};
std::uint32_t statement_id_{0};
std::uint16_t num_columns_{0};
std::uint16_t num_params_{0};
public:
prepare_stmt_response_builder() = default;
prepare_stmt_response_builder& seqnum(std::uint8_t v)
{
seqnum_ = v;
return *this;
}
prepare_stmt_response_builder& id(std::uint32_t v)
{
statement_id_ = v;
return *this;
}
prepare_stmt_response_builder& num_columns(std::uint16_t v)
{
num_columns_ = v;
return *this;
}
prepare_stmt_response_builder& num_params(std::uint16_t v)
{
num_params_ = v;
return *this;
}
std::vector<std::uint8_t> build() const
{
std::vector<std::uint8_t> res;
detail::serialization_context ctx(res, detail::disable_framing);
ctx.serialize(
detail::int1{0u}, // OK header
detail::int4{statement_id_}, // statement_id
detail::int2{num_columns_}, // num columns
detail::int2{num_params_}, // num_params
detail::int1{0u}, // reserved
detail::int2{90u} // warning_count
);
return create_frame(seqnum_, res);
}
};
} // namespace test
} // namespace mysql
} // namespace boost
#endif
@@ -22,18 +22,23 @@ namespace boost {
namespace mysql {
namespace test {
inline std::vector<std::uint8_t> create_query_body(string_view sql)
inline std::vector<std::uint8_t> create_query_body_impl(std::uint8_t command_id, string_view sql)
{
std::vector<std::uint8_t> buff;
detail::serialization_context ctx(buff, detail::disable_framing);
ctx.add(0x03);
ctx.add(command_id);
ctx.add(detail::to_span(sql));
return buff;
}
inline std::vector<std::uint8_t> create_query_frame(std::uint8_t seqnum, string_view sql)
{
return create_frame(seqnum, create_query_body(sql));
return create_frame(seqnum, create_query_body_impl(0x03, sql));
}
inline std::vector<std::uint8_t> create_prepare_statement_frame(std::uint8_t seqnum, string_view sql)
{
return create_frame(seqnum, create_query_body_impl(0x16, sql));
}
} // namespace test
+1 -1
View File
@@ -30,7 +30,7 @@ public:
: fail_after_(fail_after), err_(err)
{
}
error_code maybe_fail() noexcept { return ++num_calls_ >= fail_after_ ? err_ : error_code(); }
error_code maybe_fail() noexcept { return num_calls_++ >= fail_after_ ? err_ : error_code(); }
};
} // namespace test
@@ -1,66 +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_POOL_PRINTING_HPP
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_POOL_PRINTING_HPP
#include <boost/mysql/impl/internal/connection_pool/sansio_connection_node.hpp>
#include <ostream>
namespace boost {
namespace mysql {
namespace detail {
inline std::ostream& operator<<(std::ostream& os, connection_status v)
{
switch (v)
{
case connection_status::initial: return os << "connection_status::initial";
case connection_status::connect_in_progress: return os << "connection_status::connect_in_progress";
case connection_status::sleep_connect_failed_in_progress:
return os << "connection_status::sleep_connect_failed_in_progress";
case connection_status::reset_in_progress: return os << "connection_status::reset_in_progress";
case connection_status::ping_in_progress: return os << "connection_status::ping_in_progress";
case connection_status::idle: return os << "connection_status::idle";
case connection_status::in_use: return os << "connection_status::in_use";
default: return os << "<unknown connection_status>";
}
}
inline std::ostream& operator<<(std::ostream& os, collection_state v)
{
switch (v)
{
case collection_state::needs_collect: return os << "collection_state::needs_collect";
case collection_state::needs_collect_with_reset:
return os << "collection_state::needs_collect_with_reset";
case collection_state::none: return os << "collection_state::none";
default: return os << "<unknown collection_state>";
}
}
inline std::ostream& operator<<(std::ostream& os, next_connection_action v)
{
switch (v)
{
case next_connection_action::none: return os << "next_connection_action::none";
case next_connection_action::connect: return os << "next_connection_action::connect";
case next_connection_action::sleep_connect_failed:
return os << "next_connection_action::sleep_connect_failed";
case next_connection_action::idle_wait: return os << "next_connection_action::idle_wait";
case next_connection_action::reset: return os << "next_connection_action::reset";
case next_connection_action::ping: return os << "next_connection_action::ping";
default: return os << "<unknown next_connection_action>";
}
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+40 -60
View File
@@ -8,77 +8,57 @@
#ifndef BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/detail/results_iterator.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/detail/rows_iterator.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/db_flavor.hpp>
#include <boost/test/unit_test.hpp>
#include <ostream>
#include <iosfwd>
namespace boost {
namespace mysql {
inline std::ostream& operator<<(std::ostream& os, address_type value)
{
switch (value)
{
case address_type::host_and_port: return os << "address_type::host_and_port";
case address_type::unix_path: return os << "address_type::unix_path";
default: return os << "<unknown address_type>";
}
}
// address_type
enum class address_type;
std::ostream& operator<<(std::ostream& os, address_type value);
namespace detail {
inline std::ostream& operator<<(std::ostream& os, capabilities caps)
{
return os << "capabilities(" << caps.get() << ")";
}
// capabilities
class capabilities;
std::ostream& operator<<(std::ostream& os, const capabilities& caps);
inline std::ostream& operator<<(std::ostream& os, db_flavor value)
{
switch (value)
{
case db_flavor::mysql: return os << "mysql";
case db_flavor::mariadb: return os << "mariadb";
default: return os << "<unknown db_flavor>";
}
}
// db_flavor
enum class db_flavor;
std::ostream& operator<<(std::ostream& os, db_flavor value);
inline std::ostream& operator<<(std::ostream& os, resultset_encoding t)
{
switch (t)
{
case resultset_encoding::binary: return os << "binary";
case resultset_encoding::text: return os << "text";
default: return os << "unknown";
}
}
// resultset_encoding
enum class resultset_encoding;
std::ostream& operator<<(std::ostream& os, resultset_encoding t);
inline std::ostream& operator<<(std::ostream& os, results_iterator it)
{
return os << "results_iterator(" << static_cast<const void*>(it.obj()) << ", index=" << it.index() << ")";
}
// results_iterator
class results_iterator;
std::ostream& operator<<(std::ostream& os, const results_iterator& it);
inline std::ostream& operator<<(std::ostream& os, next_action::type_t t)
{
switch (t)
{
case next_action::type_t::none: return os << "next_action::type_t::none";
case next_action::type_t::read: return os << "next_action::type_t::read";
case next_action::type_t::write: return os << "next_action::type_t::write";
case next_action::type_t::ssl_handshake: return os << "next_action::type_t::ssl_handshake";
case next_action::type_t::ssl_shutdown: return os << "next_action::type_t::ssh_shutdown";
default: return os << "<unknown>";
}
}
// next_action_type
enum class next_action_type;
std::ostream& operator<<(std::ostream& os, next_action_type t);
// pipeline_stage_kind
enum class pipeline_stage_kind;
std::ostream& operator<<(std::ostream& os, pipeline_stage_kind v);
// pipeline_request_stage
struct pipeline_request_stage;
bool operator==(const pipeline_request_stage& lhs, const pipeline_request_stage& rhs);
std::ostream& operator<<(std::ostream& os, pipeline_request_stage v);
// connection_status (pool)
enum class connection_status;
std::ostream& operator<<(std::ostream& os, connection_status v);
// collection_state (pool)
enum class collection_state;
std::ostream& operator<<(std::ostream& os, collection_state v);
// next_connection_action (pool)
enum class next_connection_action;
std::ostream& operator<<(std::ostream& os, next_connection_action v);
} // namespace detail
} // namespace mysql
+206
View File
@@ -0,0 +1,206 @@
//
// 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/character_set.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/detail/next_action.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/results_iterator.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/impl/internal/connection_pool/sansio_connection_node.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/db_flavor.hpp>
#include <ostream>
#include "test_common/printing.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql;
// address_type
static const char* to_string(address_type v)
{
switch (v)
{
case address_type::host_and_port: return "address_type::host_and_port";
case address_type::unix_path: return "address_type::unix_path";
default: return "<unknown address_type>";
}
}
std::ostream& boost::mysql::operator<<(std::ostream& os, address_type v) { return os << ::to_string(v); }
// capabilities
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, const capabilities& v)
{
return os << "capabilities{" << v.get() << "}";
}
// db_flavor
static const char* to_string(detail::db_flavor v)
{
switch (v)
{
case detail::db_flavor::mysql: return "db_flavor::mysql";
case detail::db_flavor::mariadb: return "db_flavor::mariadb";
default: return "<unknown db_flavor>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, db_flavor v) { return os << ::to_string(v); }
// resultset_encoding
static const char* to_string(detail::resultset_encoding v)
{
switch (v)
{
case detail::resultset_encoding::text: return "resultset_encoding::text";
case detail::resultset_encoding::binary: return "resultset_encoding::binary";
default: return "<unknown resultset_encoding>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, detail::resultset_encoding v)
{
return os << ::to_string(v);
}
// results_iterator
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, const results_iterator& it)
{
return os << "results_iterator{ .self = " << static_cast<const void*>(it.obj())
<< ", .index = " << it.index() << "}";
}
// next_action_type;
static const char* to_string(detail::next_action_type v)
{
switch (v)
{
case detail::next_action_type::none: return "next_action_type::none";
case detail::next_action_type::read: return "next_action_type::read";
case detail::next_action_type::write: return "next_action_type::write";
case detail::next_action_type::ssl_handshake: return "next_action_type::ssl_handshake";
case detail::next_action_type::ssl_shutdown: return "next_action_type::ssh_shutdown";
default: return "<unknown next_action_type>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, next_action_type v)
{
return os << ::to_string(v);
}
// pipeline_stage_kind
static const char* to_string(detail::pipeline_stage_kind v)
{
switch (v)
{
case detail::pipeline_stage_kind::execute: return "pipeline_stage_kind::execute";
case detail::pipeline_stage_kind::prepare_statement: return "pipeline_stage_kind::prepare_statement";
case detail::pipeline_stage_kind::close_statement: return "pipeline_stage_kind::close_statement";
case detail::pipeline_stage_kind::reset_connection: return "pipeline_stage_kind::reset_connection";
case detail::pipeline_stage_kind::set_character_set: return "pipeline_stage_kind::set_character_set";
case detail::pipeline_stage_kind::ping: return "pipeline_stage_kind::ping";
default: return "<unknown pipeline_stage_kind>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, pipeline_stage_kind v)
{
return os << ::to_string(v);
}
// pipeline_request_stage
bool boost::mysql::detail::operator==(const pipeline_request_stage& lhs, const pipeline_request_stage& rhs)
{
if (lhs.kind != rhs.kind || lhs.seqnum != rhs.seqnum)
return false;
switch (lhs.kind)
{
case pipeline_stage_kind::execute: return lhs.stage_specific.enc == rhs.stage_specific.enc;
case pipeline_stage_kind::set_character_set:
return lhs.stage_specific.charset == rhs.stage_specific.charset;
default: return true;
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, pipeline_request_stage v)
{
os << "pipeline_request_stage{ .kind = " << v.kind << ", .seqnum = " << +v.seqnum;
switch (v.kind)
{
case pipeline_stage_kind::execute: os << ", .enc = " << v.stage_specific.enc; break;
case pipeline_stage_kind::set_character_set: os << ", .charset = " << v.stage_specific.charset; break;
default: break;
}
return os << " }";
}
// connection_status
static const char* to_string(detail::connection_status v)
{
switch (v)
{
case detail::connection_status::initial: return "connection_status::initial";
case detail::connection_status::connect_in_progress: return "connection_status::connect_in_progress";
case detail::connection_status::sleep_connect_failed_in_progress:
return "connection_status::sleep_connect_failed_in_progress";
case detail::connection_status::reset_in_progress: return "connection_status::reset_in_progress";
case detail::connection_status::ping_in_progress: return "connection_status::ping_in_progress";
case detail::connection_status::idle: return "connection_status::idle";
case detail::connection_status::in_use: return "connection_status::in_use";
default: return "<unknown connection_status>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, connection_status v)
{
return os << ::to_string(v);
}
// collection_state
static const char* to_string(detail::collection_state v)
{
switch (v)
{
case detail::collection_state::needs_collect: return "collection_state::needs_collect";
case detail::collection_state::needs_collect_with_reset:
return "collection_state::needs_collect_with_reset";
case detail::collection_state::none: return "collection_state::none";
default: return "<unknown collection_state>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, collection_state v)
{
return os << ::to_string(v);
}
static const char* to_string(detail::next_connection_action v)
{
switch (v)
{
case detail::next_connection_action::none: return "next_connection_action::none";
case detail::next_connection_action::connect: return "next_connection_action::connect";
case detail::next_connection_action::sleep_connect_failed:
return "next_connection_action::sleep_connect_failed";
case detail::next_connection_action::idle_wait: return "next_connection_action::idle_wait";
case detail::next_connection_action::reset: return "next_connection_action::reset";
case detail::next_connection_action::ping: return "next_connection_action::ping";
default: return "<unknown next_connection_action>";
}
}
std::ostream& boost::mysql::detail::operator<<(std::ostream& os, next_connection_action v)
{
return os << ::to_string(v);
}
+3 -2
View File
@@ -23,7 +23,7 @@
#include <memory>
#include "test_common/printing.hpp"
#include "test_unit/pool_printing.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql;
namespace asio = boost::asio;
@@ -47,7 +47,8 @@ struct pooled_connection_fixture
pool->params(),
ctx.get_executor(),
ctx.get_executor(),
pool->shared_state()
pool->shared_state(),
&pool->reset_pipeline_request()
)};
}
@@ -16,6 +16,9 @@
#include <boost/mysql/pool_params.hpp>
#include <boost/mysql/ssl_mode.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/impl/internal/connection_pool/connection_node.hpp>
#include <boost/mysql/impl/internal/connection_pool/connection_pool_impl.hpp>
#include <boost/mysql/impl/internal/connection_pool/internal_pool_params.hpp>
@@ -32,18 +35,20 @@
#include <boost/asio/post.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/core/span.hpp>
#include <boost/test/unit_test.hpp>
#include <chrono>
#include <cstddef>
#include <memory>
#include <ostream>
#include <utility>
#include "mock_timer.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_common/printing.hpp"
#include "test_common/tracker_executor.hpp"
#include "test_unit/pool_printing.hpp"
#include "test_unit/printing.hpp"
// These tests rely on channels, which are not compatible with this
// See https://github.com/chriskohlhoff/asio/issues/1398
@@ -65,13 +70,24 @@ using std::chrono::steady_clock;
BOOST_AUTO_TEST_SUITE(test_connection_pool_impl)
enum fn_type
enum class fn_type
{
connect,
reset,
pipeline,
ping,
};
std::ostream& operator<<(std::ostream& os, fn_type t)
{
switch (t)
{
case fn_type::connect: return os << "fn_type::connect";
case fn_type::pipeline: return os << "fn_type::pipeline";
case fn_type::ping: return os << "fn_type::ping";
default: return os << "<unknown fn_type>";
}
}
// A mock for mysql::any_connection. This allows us to control
// when and how operations like async_connect or async_ping complete,
// make assertions on the passed parameters, and force error conditions.
@@ -140,14 +156,6 @@ class mock_connection
);
}
// We use an internal async_reset_connection that allows setting the character set
template <class CompletionToken>
auto async_reset_with_charset(const character_set& charset, CompletionToken&& token)
-> decltype(op_impl(fn_type::reset, nullptr, std::forward<CompletionToken>(token)))
{
BOOST_TEST(charset.name == "utf8mb4"); // We must always use utf8mb4
return op_impl(fn_type::reset, nullptr, std::forward<CompletionToken>(token));
}
} impl_;
struct step_op
@@ -205,12 +213,27 @@ public:
}
template <class CompletionToken>
auto async_ping(CompletionToken&& token)
-> decltype(impl_.op_impl(fn_type::ping, nullptr, std::forward<CompletionToken>(token)))
auto async_ping(CompletionToken&& token
) -> decltype(impl_.op_impl(fn_type::ping, nullptr, std::forward<CompletionToken>(token)))
{
return impl_.op_impl(fn_type::ping, nullptr, std::forward<CompletionToken>(token));
}
template <class PipelineRequest, class CompletionToken>
auto async_run_pipeline(
const PipelineRequest& req,
typename PipelineRequest::response_type&,
CompletionToken&& token
) -> decltype(impl_.op_impl(fn_type::pipeline, nullptr, std::forward<CompletionToken>(token)))
{
auto req_view = detail::access::get_impl(req).to_view();
BOOST_TEST(req_view.stages.size() == 2u);
BOOST_TEST(req_view.stages[0].kind == detail::pipeline_stage_kind::reset_connection);
BOOST_TEST(req_view.stages[1].kind == detail::pipeline_stage_kind::set_character_set);
BOOST_TEST(req_view.stages[1].stage_specific.charset.name == "utf8mb4");
return impl_.op_impl(fn_type::pipeline, nullptr, std::forward<CompletionToken>(token));
}
void step(
fn_type expected_op_type,
asio::any_completion_handler<void()> handler,
@@ -629,7 +652,7 @@ BOOST_AUTO_TEST_CASE(lifecycle_reset_success)
check_shared_st(error_code(), diagnostics(), 1, 0);
// Successful reset makes the connection idle again
BOOST_ASIO_CORO_YIELD step(node, fn_type::reset);
BOOST_ASIO_CORO_YIELD step(node, fn_type::pipeline);
wait_for_status(node, connection_status::idle);
check_shared_st(error_code(), diagnostics(), 0, 1);
}
@@ -660,7 +683,7 @@ BOOST_AUTO_TEST_CASE(lifecycle_reset_error)
// Reset fails. This triggers a reconnection. Diagnostics are not saved
BOOST_ASIO_CORO_YIELD
step(node, fn_type::reset, common_server_errc::er_aborting_connection);
step(node, fn_type::pipeline, common_server_errc::er_aborting_connection);
wait_for_status(node, connection_status::connect_in_progress);
check_shared_st(error_code(), diagnostics(), 1, 0);
@@ -739,7 +762,7 @@ BOOST_AUTO_TEST_CASE(lifecycle_reset_timeout_disabled)
check_shared_st(error_code(), diagnostics(), 1, 0);
// Reset succeeds
BOOST_ASIO_CORO_YIELD step(node, fn_type::reset);
BOOST_ASIO_CORO_YIELD step(node, fn_type::pipeline);
wait_for_status(node, connection_status::idle);
check_shared_st(error_code(), diagnostics(), 0, 1);
}
@@ -1207,7 +1230,7 @@ BOOST_AUTO_TEST_CASE(get_connection_multiple_requests)
// A connection is returned. The first task to enter is served
return_connection(*node1, true);
BOOST_ASIO_CORO_YIELD step(*node1, fn_type::reset);
BOOST_ASIO_CORO_YIELD step(*node1, fn_type::pipeline);
BOOST_ASIO_CORO_YIELD wait_for_task(task3, *node1);
// The next connection to be returned is for task5
@@ -14,7 +14,7 @@
#include <cstddef>
#include "test_unit/pool_printing.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql::detail;
using boost::mysql::client_errc;
+3 -3
View File
@@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(next_action_read)
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_t::read);
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());
@@ -276,7 +276,7 @@ BOOST_AUTO_TEST_CASE(next_action_write)
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_t::write);
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());
@@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(resume_error_successive_calls)
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_t::connect);
BOOST_TEST(eng.stream().calls[0].type() == next_action_type::connect);
algo.check_calls({
{error_code(), 0u},
{error_code(), 0u}
@@ -33,7 +33,6 @@
#include <cstddef>
#include <limits>
#include <string>
#include <tuple>
#include "test_common/create_basic.hpp"
#include "test_common/printing.hpp"
@@ -53,6 +52,9 @@ using boost::mysql::detail::name_table_t;
using boost::mysql::detail::pos_absent;
using boost::mysql::detail::readable_field_traits;
// Don't attempt to print std::chrono values
BOOST_TEST_DONT_PRINT_LOG_VALUE(boost::mysql::time)
BOOST_AUTO_TEST_SUITE(test_readable_field_traits)
//
+3 -3
View File
@@ -11,18 +11,18 @@
#include <boost/test/unit_test.hpp>
#include <cstddef>
#include <cstdint>
#include <sstream>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/printing.hpp"
#include "test_common/stringize.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
// Don't attempt to print std::chrono values
BOOST_TEST_DONT_PRINT_LOG_VALUE(boost::mysql::time)
BOOST_AUTO_TEST_SUITE(test_field)
BOOST_AUTO_TEST_SUITE(constructors)
+5 -3
View File
@@ -21,11 +21,13 @@
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/printing.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
// Don't attempt to print std::chrono values
BOOST_TEST_DONT_PRINT_LOG_VALUE(boost::mysql::time)
BOOST_AUTO_TEST_SUITE(test_field_view)
BOOST_AUTO_TEST_SUITE(constructors)
@@ -645,7 +647,7 @@ BOOST_AUTO_TEST_CASE(operator_stream)
field_view input;
string_view expected;
} test_cases[] = {
// Field views holding values
// Field views holding values
{"null", field_view(), "<NULL>"},
{"i64_positive", field_view(std::int64_t(42)), "42"},
{"i64_negative", field_view(std::int64_t(-90)), "-90"},
@@ -666,7 +668,7 @@ BOOST_AUTO_TEST_CASE(operator_stream)
{"datetime", field_view(datetime(2020, 1, 19, 11, 30, 21, 98765)), "2020-01-19 11:30:21.098765"},
{"time", field_view(-maket(12, 1, 5, 345)), "-12:01:05.000345"},
// Field views holding pointers to fields
// Field views holding pointers to fields
{"ref_null", field_view(owning_fields.f_null), "<NULL>"},
{"ref_int64", field_view(owning_fields.f_int64), "-1"},
{"ref_uint64", field_view(owning_fields.f_uint64), "50"},
+2 -1
View File
@@ -12,7 +12,8 @@
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/coldef_view.hpp>
#include "test_common/printing.hpp"
#include <boost/test/unit_test.hpp>
#include "test_unit/create_meta.hpp"
using namespace boost::mysql;
+767
View File
@@ -0,0 +1,767 @@
//
// 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/blob.hpp>
#include <boost/mysql/character_set.hpp>
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/pipeline.hpp>
#include <boost/mysql/results.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/pipeline_concepts.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/core/span.hpp>
#include <boost/optional/optional.hpp>
#include <boost/system/result.hpp>
#include <boost/test/unit_test.hpp>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/buffer_concat.hpp"
#include "test_common/create_basic.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_common/printing.hpp"
#include "test_unit/create_execution_processor.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_ok.hpp"
#include "test_unit/create_query_frame.hpp"
#include "test_unit/create_statement.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql;
using namespace boost::mysql::test;
using boost::test_tools::per_element;
using detail::pipeline_request_stage;
using detail::pipeline_stage_kind;
using detail::resultset_encoding;
BOOST_AUTO_TEST_SUITE(test_pipeline)
// Validates the exception thrown when adding a statement execution with wrong number of params
static auto stmt_exc_validator = [](const std::invalid_argument& exc) {
BOOST_TEST(
string_view(exc.what()) == "Wrong number of actual parameters supplied to a prepared statement"
);
return true;
};
BOOST_AUTO_TEST_SUITE(any_stage_response_)
BOOST_AUTO_TEST_CASE(default_ctor)
{
// Construct
any_stage_response r;
// Contains an empty error
BOOST_TEST(!r.has_results());
BOOST_TEST(!r.has_statement());
BOOST_TEST(r.error() == errcode_with_diagnostics{});
}
BOOST_AUTO_TEST_CASE(underlying_error)
{
// Setup
any_stage_response r;
detail::access::get_impl(r).set_error(client_errc::invalid_encoding, create_server_diag("my_message"));
// Check
BOOST_TEST(!r.has_results());
BOOST_TEST(!r.has_statement());
BOOST_TEST(
r.error() ==
(errcode_with_diagnostics{client_errc::invalid_encoding, create_server_diag("my_message")})
);
}
BOOST_AUTO_TEST_CASE(underlying_statement)
{
// Setup
any_stage_response r;
detail::access::get_impl(r).set_result(statement_builder().id(3).build());
// Check
BOOST_TEST(!r.has_results());
BOOST_TEST(r.has_statement());
BOOST_TEST(r.as_statement().id() == 3u);
BOOST_TEST(r.get_statement().id() == 3u);
// error() can be called and returns an empty error
BOOST_TEST(r.error() == errcode_with_diagnostics{});
}
BOOST_AUTO_TEST_CASE(underlying_results)
{
// Setup
any_stage_response r;
detail::access::get_impl(r).emplace_results();
add_ok(detail::access::get_impl(r).get_processor(), ok_builder().info("some_info").build());
// Check
BOOST_TEST(r.has_results());
BOOST_TEST(!r.has_statement());
BOOST_TEST(r.get_results().info() == "some_info");
BOOST_TEST(r.as_results().info() == "some_info");
// Rvalue reference accessors work
results&& ref1 = std::move(r).get_results();
results&& ref2 = std::move(r).as_results();
boost::ignore_unused(ref1);
boost::ignore_unused(ref2);
// error() can be called and returns an empty error
BOOST_TEST(r.error() == errcode_with_diagnostics{});
}
BOOST_AUTO_TEST_CASE(as_results_error)
{
// Empty error
any_stage_response r;
BOOST_CHECK_THROW(r.as_results(), std::invalid_argument);
BOOST_CHECK_THROW(std::move(r).as_results(), std::invalid_argument);
// Non-empty error
detail::access::get_impl(r).set_error(client_errc::extra_bytes, create_client_diag("my_msg"));
BOOST_CHECK_THROW(r.as_results(), std::invalid_argument);
BOOST_CHECK_THROW(std::move(r).as_results(), std::invalid_argument);
// Statement
detail::access::get_impl(r).set_result(statement_builder().build());
BOOST_CHECK_THROW(r.as_results(), std::invalid_argument);
BOOST_CHECK_THROW(std::move(r).as_results(), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(as_statement_error)
{
// Empty error
any_stage_response r;
BOOST_CHECK_THROW(r.as_statement(), std::invalid_argument);
// Non-empty error
detail::access::get_impl(r).set_error(client_errc::extra_bytes, create_client_diag("my_msg"));
BOOST_CHECK_THROW(r.as_statement(), std::invalid_argument);
// results
detail::access::get_impl(r).emplace_results();
BOOST_CHECK_THROW(r.as_statement(), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(change_type)
{
any_stage_response r;
// Set results
detail::access::get_impl(r).emplace_results();
BOOST_TEST(r.has_results());
// Set an error
detail::access::get_impl(r).set_error(client_errc::extra_bytes, create_client_diag("abc"));
BOOST_TEST(!r.has_results());
BOOST_TEST(r.error() == (errcode_with_diagnostics{client_errc::extra_bytes, create_client_diag("abc")}));
// Reset the error
detail::access::get_impl(r).emplace_error();
BOOST_TEST(r.error() == errcode_with_diagnostics{});
// Set a statement
detail::access::get_impl(r).set_result(statement_builder().build());
BOOST_TEST(r.has_statement());
// Set results again
detail::access::get_impl(r).emplace_results();
BOOST_TEST(r.has_results());
BOOST_TEST(!r.has_statement());
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(stage_creation)
// Helper to run any successful stage creation test
template <class PipelineStageType>
static void check_stage_creation(
PipelineStageType stage,
const std::vector<std::uint8_t>& expected_buffer,
pipeline_request_stage expected_stage
)
{
// Serialize the request
std::vector<std::uint8_t> buff{0xde, 0xad};
auto actual_stage = detail::pipeline_stage_access::create(stage, buff);
// Check
std::vector<std::uint8_t> expected{0xde, 0xad};
expected.insert(expected.end(), expected_buffer.begin(), expected_buffer.end());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
BOOST_TEST(actual_stage == expected_stage);
}
BOOST_AUTO_TEST_CASE(execute_text_query)
{
check_stage_creation(
execute_stage("SELECT 1"),
create_query_frame(0, "SELECT 1"),
{pipeline_stage_kind::execute, 1u, resultset_encoding::text}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_individual_parameters)
{
check_stage_creation(
execute_stage(statement_builder().id(2).num_params(3).build(), {42, "abc", nullptr}),
{0x1e, 0x00, 0x00, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x01, 0x08, 0x00, 0xfe, 0x00, 0x06, 0x00, 0x2a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63},
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_individual_fields_no_params)
{
// We run the required writable field transformations
check_stage_creation(
execute_stage(statement_builder().id(2).num_params(0).build(), {}),
{0x0a, 0x00, 0x00, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_individual_fields_writable_fields)
{
// We run the required writable field transformations
check_stage_creation(
execute_stage(
statement_builder().id(2).num_params(3).build(),
{boost::optional<int>(42), "abc", boost::optional<int>()}
),
{0x1e, 0x00, 0x00, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x01, 0x08, 0x00, 0xfe, 0x00, 0x06, 0x00, 0x2a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63},
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_individual_fields_manyfields)
{
// There is an optimization to avoid allocations for statements with less than 64 fields
check_stage_creation(
execute_stage(
statement_builder().id(1).num_params(65).build(),
{"1", "2", "3", nullptr, nullptr, nullptr, "a", "b", "c", nullptr,
nullptr, nullptr, 13, 14, nullptr, nullptr, nullptr, 18, "h", "jk",
21, nullptr, nullptr, nullptr, 25, nullptr, nullptr, nullptr, 29, 30,
nullptr, nullptr, nullptr, 34, 35, nullptr, nullptr, nullptr, 39, 40,
nullptr, nullptr, nullptr, 44, 45, nullptr, nullptr, nullptr, 49, 50,
nullptr, nullptr, nullptr, 54, nullptr, nullptr, nullptr, 58, nullptr, nullptr,
nullptr, 62, "U", "VV", nullptr}
),
{0x3c, 0x01, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0xce, 0xe1,
0xce, 0x39, 0xe7, 0xdc, 0x1d, 0x01, 0x01, 0xfe, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x06, 0x00, 0x06, 0x00,
0x06, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08,
0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x08, 0x00, 0x06, 0x00,
0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06,
0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00,
0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06,
0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00,
0x06, 0x00, 0x08, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x08, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x06,
0x00, 0x01, 0x31, 0x01, 0x32, 0x01, 0x33, 0x01, 0x61, 0x01, 0x62, 0x01, 0x63, 0x0d, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x68, 0x02, 0x6a, 0x6b, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x02, 0x56, 0x56},
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_range)
{
auto fv_arr = make_fv_arr(42, "abc", nullptr);
check_stage_creation(
execute_stage(statement_builder().id(2).num_params(3).build(), fv_arr),
{0x1e, 0x00, 0x00, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x01, 0x08, 0x00, 0xfe, 0x00, 0x06, 0x00, 0x2a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63},
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary}
);
}
BOOST_AUTO_TEST_CASE(execute_statement_error)
{
std::vector<std::uint8_t> buff;
// Individual fields, too few parameters
BOOST_CHECK_EXCEPTION(
detail::pipeline_stage_access::create(
execute_stage(statement_builder().num_params(2).build(), {10}),
buff
),
std::invalid_argument,
stmt_exc_validator
);
// Individual fields, too many parameters
BOOST_CHECK_EXCEPTION(
detail::pipeline_stage_access::create(
execute_stage(statement_builder().num_params(2).build(), {10, 20, 30}),
buff
),
std::invalid_argument,
stmt_exc_validator
);
// Range, too few parameters
auto fv_arr = make_fv_arr(42);
BOOST_CHECK_EXCEPTION(
detail::pipeline_stage_access::create(
execute_stage(
statement_builder().num_params(2).build(),
{fv_arr.data(), fv_arr.data() + fv_arr.size()}
),
buff
),
std::invalid_argument,
stmt_exc_validator
);
}
//
// prepare statement
//
BOOST_AUTO_TEST_CASE(prepare_statement)
{
check_stage_creation(
prepare_statement_stage("SELECT 1"),
create_prepare_statement_frame(0, "SELECT 1"),
{pipeline_stage_kind::prepare_statement, 1u, {}}
);
}
BOOST_AUTO_TEST_CASE(prepare_statement_empty)
{
// Empty prepare statements don't produce problems
check_stage_creation(
prepare_statement_stage(""),
create_prepare_statement_frame(0, ""),
{pipeline_stage_kind::prepare_statement, 1u, {}}
);
}
//
// close statement
//
BOOST_AUTO_TEST_CASE(close_statement)
{
check_stage_creation(
close_statement_stage(statement_builder().id(3).num_params(1).build()),
create_frame(0, {0x19, 0x03, 0x00, 0x00, 0x00}),
{pipeline_stage_kind::close_statement, 1u, {}}
);
}
//
// reset connection
//
BOOST_AUTO_TEST_CASE(reset_connection)
{
check_stage_creation(
reset_connection_stage(),
create_frame(0, {0x1f}),
{pipeline_stage_kind::reset_connection, 1u, {}}
);
}
//
// set character set
//
BOOST_AUTO_TEST_CASE(set_character_set)
{
check_stage_creation(
set_character_set_stage(utf8mb4_charset),
create_query_frame(0, "SET NAMES 'utf8mb4'"),
{pipeline_stage_kind::set_character_set, 1u, utf8mb4_charset}
);
}
BOOST_AUTO_TEST_CASE(set_character_set_escapes)
{
// We don't create SQL injection vulnerabilities while composing SET NAMES
character_set charset{"inj'ection", utf8mb4_charset.next_char};
check_stage_creation(
set_character_set_stage(charset),
create_query_frame(0, "SET NAMES 'inj\\'ection'"),
{pipeline_stage_kind::set_character_set, 1u, charset}
);
}
BOOST_AUTO_TEST_CASE(set_character_set_error)
{
// If a character set name that can't be securely escaped gets passed, we throw
std::vector<std::uint8_t> buff;
BOOST_CHECK_EXCEPTION(
detail::pipeline_stage_access::create(
set_character_set_stage(character_set{"bad\xff", utf8mb4_charset.next_char}),
buff
),
std::invalid_argument,
[](const std::invalid_argument& exc) {
BOOST_TEST(string_view(exc.what()) == "Invalid character set name");
return true;
}
);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(dynamic_interface)
// Adding stages work
BOOST_AUTO_TEST_CASE(add)
{
// Default ctor creates an empty request
pipeline_request req;
auto view = detail::access::get_impl(req).to_view();
std::vector<pipeline_request_stage> expected_stages;
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, blob{});
// Add a reset connection stage
req.add(reset_connection_stage());
view = detail::access::get_impl(req).to_view();
expected_stages = {
{pipeline_stage_kind::reset_connection, 1u, {}}
};
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, create_frame(0, {0x1f}));
// Add an execution stage
req.add(execute_stage("SELECT 1"));
view = detail::access::get_impl(req).to_view();
expected_stages = {
{pipeline_stage_kind::reset_connection, 1u, {} },
{pipeline_stage_kind::execute, 1u, resultset_encoding::text}
};
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
view.buffer,
concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SELECT 1"))
);
}
// All stage types work
BOOST_AUTO_TEST_CASE(all_stage_kinds)
{
// Setup
pipeline_request req;
// Add stages
req.add(reset_connection_stage())
.add(execute_stage("SELECT 1"))
.add(prepare_statement_stage("SELECT ?"))
.add(set_character_set_stage(utf8mb4_charset))
.add(close_statement_stage(statement_builder().id(8).build()));
// Expected values
auto expected_buffer = buffer_builder()
.add(create_frame(0, {0x1f}))
.add(create_query_frame(0, "SELECT 1"))
.add(create_prepare_statement_frame(0, "SELECT ?"))
.add(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.add(create_frame(0, {0x19, 0x08, 0x00, 0x00, 0x00}))
.build();
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::reset_connection, 1u, {} },
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
{pipeline_stage_kind::prepare_statement, 1u, {} },
{pipeline_stage_kind::set_character_set, 1u, utf8mb4_charset },
{pipeline_stage_kind::close_statement, 1u, {} },
};
// Check
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, expected_buffer);
}
BOOST_AUTO_TEST_CASE(clear_previous_contents)
{
// Create a pipeline request with some steps
pipeline_request req;
req.add(reset_connection_stage())
.add(set_character_set_stage(utf8mb4_charset))
.add(execute_stage("SELECT 1"));
// Clear the pipeline
req.clear();
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == std::vector<pipeline_request_stage>{}, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, blob{});
// Add some stages again
req.add(execute_stage("abc")).add(close_statement_stage(statement_builder().id(7).build()));
// Check
view = detail::access::get_impl(req).to_view();
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
{pipeline_stage_kind::close_statement, 1u, {} },
};
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
view.buffer,
concat_copy(create_query_frame(0, "abc"), create_frame(0, {0x19, 0x07, 0x00, 0x00, 0x00}))
);
}
BOOST_AUTO_TEST_CASE(clear_empty)
{
// Clearing an empty pipeline is a no-op
pipeline_request req;
req.clear();
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == std::vector<pipeline_request_stage>{}, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, blob{});
}
BOOST_AUTO_TEST_CASE(add_error)
{
// Spotcheck: add propagates errors raised by serialization functions
pipeline_request req;
BOOST_CHECK_EXCEPTION(
req.add(execute_stage(statement_builder().num_params(2).build(), {42})),
std::invalid_argument,
stmt_exc_validator
);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(static_interface)
BOOST_AUTO_TEST_CASE(all_stage_kinds)
{
// Setup
auto req = make_pipeline_request(
reset_connection_stage(),
execute_stage("SELECT 1"),
prepare_statement_stage("SELECT ?"),
set_character_set_stage(utf8mb4_charset),
close_statement_stage(statement_builder().id(8).build())
);
using expected_type = static_pipeline_request<
reset_connection_stage,
execute_stage,
prepare_statement_stage,
set_character_set_stage,
close_statement_stage>;
static_assert(std::is_same<decltype(req), expected_type>::value, "Type deduction error");
// Expected values
auto expected_buffer = buffer_builder()
.add(create_frame(0, {0x1f}))
.add(create_query_frame(0, "SELECT 1"))
.add(create_prepare_statement_frame(0, "SELECT ?"))
.add(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.add(create_frame(0, {0x19, 0x08, 0x00, 0x00, 0x00}))
.build();
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::reset_connection, 1u, {} },
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
{pipeline_stage_kind::prepare_statement, 1u, {} },
{pipeline_stage_kind::set_character_set, 1u, utf8mb4_charset },
{pipeline_stage_kind::close_statement, 1u, {} },
};
// Check
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, expected_buffer);
}
BOOST_AUTO_TEST_CASE(repeated_stage_kinds)
{
// Spotcheck: repeated stage kinds don't create problems
auto req = make_pipeline_request(
execute_stage("SELECT 1"),
execute_stage("SELECT 2"),
prepare_statement_stage("SELECT ?"),
prepare_statement_stage("COMMIT")
);
using expected_type = static_pipeline_request<
execute_stage,
execute_stage,
prepare_statement_stage,
prepare_statement_stage>;
static_assert(std::is_same<decltype(req), expected_type>::value, "Type deduction error");
// Expected values
auto expected_buffer = buffer_builder()
.add(create_query_frame(0, "SELECT 1"))
.add(create_query_frame(0, "SELECT 2"))
.add(create_prepare_statement_frame(0, "SELECT ?"))
.add(create_prepare_statement_frame(0, "COMMIT"))
.build();
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
{pipeline_stage_kind::prepare_statement, 1u, {} },
{pipeline_stage_kind::prepare_statement, 1u, {} },
};
// Check
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, expected_buffer);
}
BOOST_AUTO_TEST_CASE(statement_execution_lifetimes)
{
// Spotcheck: we don't create lifetime problems when adding statement executions
std::string param = "abc";
auto req = make_pipeline_request(
execute_stage(statement_builder().id(2).num_params(3).build(), {42, param, nullptr})
);
param = "uuu";
// Expected values
const std::uint8_t expected_buffer[] = {0x1e, 0x00, 0x00, 0x00, 0x17, 0x02, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x08, 0x00,
0xfe, 0x00, 0x06, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63};
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::execute, 1u, resultset_encoding::binary},
};
// Check
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, expected_buffer);
}
BOOST_AUTO_TEST_CASE(assignment)
{
// Spotcheck: assignment works as expected
// Setup
auto req = make_pipeline_request(
prepare_statement_stage("START TRANSACTION"),
execute_stage(statement_builder().id(1).num_params(0).build(), {})
);
req = {prepare_statement_stage("COMMIT"), execute_stage("SELECT 2")};
// Expected values
auto expected_buffer = buffer_builder()
.add(create_prepare_statement_frame(0, "COMMIT"))
.add(create_query_frame(0, "SELECT 2"))
.build();
const pipeline_request_stage expected_stages[] = {
{pipeline_stage_kind::prepare_statement, 1u, {} },
{pipeline_stage_kind::execute, 1u, resultset_encoding::text},
};
// Check
auto view = detail::access::get_impl(req).to_view();
BOOST_TEST(view.stages == expected_stages, per_element());
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(view.buffer, expected_buffer);
}
BOOST_AUTO_TEST_CASE(ctor_error)
{
// Spotcheck: ctor propagates errors raised by serialization functions
BOOST_CHECK_EXCEPTION(
make_pipeline_request(execute_stage(statement_builder().num_params(2).build(), {42})),
std::invalid_argument,
stmt_exc_validator
);
}
#ifndef BOOST_NO_CXX17_DEDUCTION_GUIDES
BOOST_AUTO_TEST_CASE(deduction_guide)
{
static_pipeline_request req(
reset_connection_stage(),
execute_stage("SELECT 1"),
prepare_statement_stage("SELECT ?"),
set_character_set_stage(utf8mb4_charset),
close_statement_stage(statement_builder().id(8).build()),
execute_stage("SELECT 1")
);
using expected_type = static_pipeline_request<
reset_connection_stage,
execute_stage,
prepare_statement_stage,
set_character_set_stage,
close_statement_stage,
execute_stage>;
static_assert(std::is_same<decltype(req), expected_type>::value, "Deduction guide is incorrect");
boost::ignore_unused(req);
}
#endif
BOOST_AUTO_TEST_SUITE_END()
// errcode_with_diagnostics can be used with system::result
BOOST_AUTO_TEST_CASE(errcode_with_diagnostics_result)
{
boost::system::result<int, errcode_with_diagnostics> r = errcode_with_diagnostics{
client_errc::cancelled,
create_server_diag("my_message")
};
BOOST_CHECK_EXCEPTION(r.value(), error_with_diagnostics, [](const error_with_diagnostics& exc) {
BOOST_TEST(exc.code() == client_errc::cancelled);
BOOST_TEST(exc.get_diagnostics() == create_server_diag("my_message"));
return true;
});
}
// pipeline concepts
using detail::is_pipeline_stage_type;
static_assert(is_pipeline_stage_type<execute_stage>::value, "");
static_assert(is_pipeline_stage_type<prepare_statement_stage>::value, "");
static_assert(is_pipeline_stage_type<close_statement_stage>::value, "");
static_assert(is_pipeline_stage_type<reset_connection_stage>::value, "");
static_assert(is_pipeline_stage_type<set_character_set_stage>::value, "");
static_assert(!is_pipeline_stage_type<execute_stage&>::value, "");
static_assert(!is_pipeline_stage_type<const execute_stage&>::value, "");
static_assert(!is_pipeline_stage_type<const execute_stage>::value, "");
static_assert(!is_pipeline_stage_type<int>::value, "");
static_assert(!is_pipeline_stage_type<pipeline_request>::value, "");
using detail::is_pipeline_request_type;
static_assert(is_pipeline_request_type<pipeline_request>::value, "");
static_assert(is_pipeline_request_type<static_pipeline_request<execute_stage>>::value, "");
static_assert(is_pipeline_request_type<static_pipeline_request<close_statement_stage>>::value, "");
static_assert(
is_pipeline_request_type<static_pipeline_request<
execute_stage,
reset_connection_stage,
prepare_statement_stage,
close_statement_stage,
set_character_set_stage>>::value,
""
);
static_assert(!is_pipeline_request_type<const pipeline_request>::value, "");
static_assert(!is_pipeline_request_type<pipeline_request&>::value, "");
static_assert(!is_pipeline_request_type<const pipeline_request&>::value, "");
static_assert(!is_pipeline_request_type<int>::value, "");
BOOST_AUTO_TEST_SUITE_END()
+4 -4
View File
@@ -11,6 +11,7 @@
#include <boost/mysql/statement.hpp>
#include <boost/mysql/impl/internal/sansio/close_statement.hpp>
#include <boost/mysql/impl/internal/sansio/run_pipeline.hpp>
#include <boost/test/unit_test.hpp>
@@ -26,6 +27,8 @@
using namespace boost::mysql::test;
using namespace boost::mysql;
// setup_close_statement_pipeline: running a pipeline with these parameters
// has the intended effect
BOOST_AUTO_TEST_SUITE(test_close_statement)
// A close_statement request pipelined with a ping (frame headers included)
@@ -36,10 +39,7 @@ static std::vector<std::uint8_t> expected_request()
struct fixture : algo_fixture_base
{
detail::close_statement_algo algo{
st,
{&diag, 3}
};
detail::run_pipeline_algo algo{detail::setup_close_statement_pipeline(st, {&diag, 3})};
};
BOOST_AUTO_TEST_CASE(success)
+148 -86
View File
@@ -16,12 +16,10 @@
#include <boost/test/unit_test.hpp>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/buffer_concat.hpp"
#include "test_common/check_meta.hpp"
#include "test_unit/algo_test.hpp"
#include "test_unit/create_coldef_frame.hpp"
#include "test_unit/create_execution_processor.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_meta.hpp"
#include "test_unit/create_ok.hpp"
@@ -39,21 +37,156 @@ using boost::mysql::detail::resultset_encoding;
BOOST_AUTO_TEST_SUITE(test_execute)
struct fixture : algo_fixture_base
// The serialized form of a SELECT 1 query request
static constexpr std::uint8_t serialized_select_1[] = {0x03, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x20, 0x31};
struct read_response_fixture : algo_fixture_base
{
mock_execution_processor proc;
detail::read_execute_response_algo algo{&diag, &proc};
read_response_fixture() { proc.sequence_number() = 42; }
};
BOOST_AUTO_TEST_CASE(read_response_eof)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(create_ok_frame(42, ok_builder().affected_rows(60u).info("abc").build()))
.check(fix);
// Verify
fix.proc.num_calls().on_head_ok_packet(1).validate();
BOOST_TEST(fix.proc.affected_rows() == 60u);
BOOST_TEST(fix.proc.info() == "abc");
}
BOOST_AUTO_TEST_CASE(read_response_single_row_batch)
{
// Setup
read_response_fixture fix;
// Run the algo. Rows and OK are received in a single go (one call to read_some_rows)
algo_test()
.expect_read(create_frame(42, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(43, meta_builder().type(column_type::bigint).build_coldef()))
.expect_read(buffer_builder()
.add(create_text_row_message(44, 42))
.add(create_text_row_message(45, 43))
.add(create_eof_frame(46, ok_builder().affected_rows(10u).info("1st").build()))
.build())
.check(fix);
// Verify
fix.proc.num_calls()
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(1)
.on_row(2)
.on_row_batch_finish(1)
.on_row_ok_packet(1)
.validate();
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
BOOST_TEST(fix.proc.num_meta() == 1u);
check_meta(fix.proc.meta(), {column_type::bigint});
BOOST_TEST(fix.proc.affected_rows() == 10u);
BOOST_TEST(fix.proc.info() == "1st");
}
BOOST_AUTO_TEST_CASE(read_response_multiple_row_batches)
{
// Setup
read_response_fixture fix;
// Run the algo. Multiple read_some_rows calls are required
algo_test()
.expect_read(create_frame(42, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(43, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(create_text_row_message(44, 42))
.expect_read(create_text_row_message(45, 43))
.expect_read(create_eof_frame(46, ok_builder().affected_rows(10u).info("1st").build()))
.check(fix);
// Verify
fix.proc.num_calls()
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(3)
.on_row(2)
.on_row_batch_finish(3)
.on_row_ok_packet(1)
.validate();
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
BOOST_TEST(fix.proc.num_meta() == 1u);
check_meta(fix.proc.meta(), {column_type::tinyint});
BOOST_TEST(fix.proc.affected_rows() == 10u);
BOOST_TEST(fix.proc.info() == "1st");
}
BOOST_AUTO_TEST_CASE(read_response_multiple_resultsets)
{
// Setup
read_response_fixture fix;
// Run the algo. Multiple read_some_rows calls are required
algo_test()
.expect_read(create_frame(42, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(43, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(create_text_row_message(44, 42))
.expect_read(
create_eof_frame(45, ok_builder().affected_rows(10u).info("1st").more_results(true).build())
)
.expect_read(create_frame(46, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(47, meta_builder().type(column_type::varchar).build_coldef()))
.expect_read(
create_eof_frame(48, ok_builder().affected_rows(11u).info("2nd").more_results(true).build())
)
.expect_read(create_ok_frame(49, ok_builder().affected_rows(12u).info("3rd").build()))
.check(fix);
// Verify
fix.proc.num_calls()
.on_num_meta(2)
.on_meta(2)
.on_row_batch_start(3)
.on_row(1)
.on_row_batch_finish(3)
.on_row_ok_packet(2)
.on_head_ok_packet(1)
.validate();
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
BOOST_TEST(fix.proc.num_meta() == 1u);
BOOST_TEST(fix.proc.affected_rows() == 12u);
BOOST_TEST(fix.proc.info() == "3rd");
}
// Tests error on write, while reading head and while reading rows (error spotcheck)
BOOST_AUTO_TEST_CASE(read_response_error_network_error)
{
algo_test()
.expect_read(create_frame(42, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(43, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(create_text_row_message(44, 42))
.expect_read(create_text_row_message(45, 43))
.expect_read(create_eof_frame(46, ok_builder().affected_rows(10u).info("1st").build()))
.check_network_errors<read_response_fixture>();
}
struct execute_fixture : algo_fixture_base
{
mock_execution_processor proc;
detail::execute_algo algo;
fixture(any_execution_request req = {"SELECT 1"}) : algo(st, {&diag, req, &proc}) {}
execute_fixture(any_execution_request req = {"SELECT 1"}) : algo({&diag, req, &proc}) {}
};
// The serialized form of a SELECT 1 query request
static constexpr std::uint8_t serialized_select_1[] = {0x03, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x20, 0x31};
BOOST_AUTO_TEST_CASE(eof)
BOOST_AUTO_TEST_CASE(execute_success_eof)
{
// Setup
fixture fix;
execute_fixture fix;
// Run the algo
algo_test()
@@ -68,10 +201,10 @@ BOOST_AUTO_TEST_CASE(eof)
BOOST_TEST(fix.proc.info() == "abc");
}
BOOST_AUTO_TEST_CASE(single_row_batch)
BOOST_AUTO_TEST_CASE(execute_success_rows)
{
// Setup
fixture fix;
execute_fixture fix;
// Run the algo. Rows and OK are received in a single go (one call to read_some_rows)
algo_test()
@@ -102,84 +235,13 @@ BOOST_AUTO_TEST_CASE(single_row_batch)
BOOST_TEST(fix.proc.info() == "1st");
}
BOOST_AUTO_TEST_CASE(multiple_row_batches)
{
// Setup
fixture fix;
// Run the algo. Multiple read_some_rows calls are required
algo_test()
.expect_write(create_frame(0, serialized_select_1))
.expect_read(create_frame(1, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(2, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(create_text_row_message(3, 42))
.expect_read(create_text_row_message(4, 43))
.expect_read(create_eof_frame(5, ok_builder().affected_rows(10u).info("1st").build()))
.check(fix);
// Verify
fix.proc.num_calls()
.reset(1)
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(3)
.on_row(2)
.on_row_batch_finish(3)
.on_row_ok_packet(1)
.validate();
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
BOOST_TEST(fix.proc.num_meta() == 1u);
check_meta(fix.proc.meta(), {column_type::tinyint});
BOOST_TEST(fix.proc.affected_rows() == 10u);
BOOST_TEST(fix.proc.info() == "1st");
}
BOOST_AUTO_TEST_CASE(multiple_resultsets)
{
// Setup
fixture fix;
// Run the algo. Multiple read_some_rows calls are required
algo_test()
.expect_write(create_frame(0, serialized_select_1))
.expect_read(create_frame(1, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(2, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(create_text_row_message(3, 42))
.expect_read(
create_eof_frame(4, ok_builder().affected_rows(10u).info("1st").more_results(true).build())
)
.expect_read(create_frame(5, {0x01})) // OK, 1 column
.expect_read(create_coldef_frame(6, meta_builder().type(column_type::varchar).build_coldef()))
.expect_read(
create_eof_frame(7, ok_builder().affected_rows(11u).info("2nd").more_results(true).build())
)
.expect_read(create_ok_frame(8, ok_builder().affected_rows(12u).info("3rd").build()))
.check(fix);
// Verify
fix.proc.num_calls()
.reset(1)
.on_num_meta(2)
.on_meta(2)
.on_row_batch_start(3)
.on_row(1)
.on_row_batch_finish(3)
.on_row_ok_packet(2)
.on_head_ok_packet(1)
.validate();
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
BOOST_TEST(fix.proc.num_meta() == 1u);
BOOST_TEST(fix.proc.affected_rows() == 12u);
BOOST_TEST(fix.proc.info() == "3rd");
}
// Test that immediate completion with errors in start_execution are propagated correctlty
BOOST_AUTO_TEST_CASE(error_num_params)
BOOST_AUTO_TEST_CASE(execute_error_num_params)
{
// Setup
auto stmt = statement_builder().id(1).num_params(2).build();
const auto params = make_fv_arr("test", nullptr, 42); // too many params
fixture fix(any_execution_request(stmt, params));
execute_fixture fix(any_execution_request(stmt, params));
// Run the algo. Nothing should be written to the server
algo_test().check(fix, client_errc::wrong_num_params);
@@ -189,7 +251,7 @@ BOOST_AUTO_TEST_CASE(error_num_params)
}
// Tests error on write, while reading head and while reading rows (error spotcheck)
BOOST_AUTO_TEST_CASE(error_network_error)
BOOST_AUTO_TEST_CASE(execute_error_network_error)
{
algo_test()
.expect_write(create_frame(0, serialized_select_1))
@@ -198,7 +260,7 @@ BOOST_AUTO_TEST_CASE(error_network_error)
.expect_read(create_text_row_message(3, 42))
.expect_read(create_text_row_message(4, 43))
.expect_read(create_eof_frame(5, ok_builder().affected_rows(10u).info("1st").build()))
.check_network_errors<fixture>();
.check_network_errors<execute_fixture>();
}
BOOST_AUTO_TEST_SUITE_END()
-323
View File
@@ -1,323 +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/impl/internal/sansio/message_writer.hpp>
#include <boost/test/unit_test.hpp>
#include <cstdint>
#include <vector>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/buffer_concat.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/mock_message.hpp"
using namespace boost::mysql::detail;
using namespace boost::mysql::test;
using boost::span;
namespace {
BOOST_AUTO_TEST_SUITE(test_message_writer)
BOOST_AUTO_TEST_CASE(regular_message)
{
message_writer writer;
const std::vector<std::uint8_t> msg_body{0x01, 0x02, 0x03};
std::uint8_t seqnum = 2;
// Operation start
writer.prepare_write(mock_message{msg_body}, seqnum);
BOOST_TEST(!writer.done());
// First (and only) chunk
auto chunk = writer.current_chunk();
auto expected = create_frame(2, msg_body);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful
writer.resume(7);
BOOST_TEST(seqnum == 3u);
BOOST_TEST(writer.done());
}
BOOST_AUTO_TEST_CASE(short_writes)
{
message_writer writer;
const std::vector<std::uint8_t> msg_body{0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
std::uint8_t seqnum = 2;
// Operation start
writer.prepare_write(mock_message{msg_body}, seqnum);
// First chunk
auto chunk = writer.current_chunk();
auto expected = create_frame(2, msg_body);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// Write signals partial write
writer.resume(3);
BOOST_TEST(seqnum == 3u);
BOOST_TEST(!writer.done());
// Remaining of the chunk
chunk = writer.current_chunk();
span<const std::uint8_t> expected_buff(expected.data() + 3, 7);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected_buff);
// Another partial write
writer.resume(2);
BOOST_TEST(seqnum == 3u);
BOOST_TEST(!writer.done());
// Remaining of the chunk
chunk = writer.current_chunk();
expected_buff = span<const std::uint8_t>(expected.data() + 5, 5);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected_buff);
// Zero bytes partial writes work correctly
BOOST_TEST(seqnum == 3u);
BOOST_TEST(!writer.done());
// Remaining of the chunk
chunk = writer.current_chunk();
expected_buff = span<const std::uint8_t>(expected.data() + 5, 5);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected_buff);
// Final bytes
writer.resume(5);
BOOST_TEST(seqnum == 3u);
BOOST_TEST(writer.done());
}
BOOST_AUTO_TEST_CASE(several_messages)
{
message_writer writer;
std::vector<std::uint8_t> msg_1{0x01, 0x02, 0x04};
std::vector<std::uint8_t> msg_2{0x04, 0x05, 0x06, 0x09, 0xff};
std::vector<std::uint8_t> msg_3{0x02, 0xab};
std::uint8_t seqnum_1 = 2;
std::uint8_t seqnum_2 = 42;
std::uint8_t seqnum_3 = 21;
// Operation start for message 1
writer.prepare_write(mock_message{msg_1}, seqnum_1);
BOOST_TEST(!writer.done());
// Chunk 1
auto chunk = writer.current_chunk();
auto expected = create_frame(2, msg_1);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 1
writer.resume(7);
BOOST_TEST(seqnum_1 == 3u);
BOOST_TEST(writer.done());
// Operation start for message 2
writer.prepare_write(mock_message{msg_2}, seqnum_2);
BOOST_TEST(!writer.done());
// Chunk 2
chunk = writer.current_chunk();
expected = create_frame(42, msg_2);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 2
writer.resume(9);
BOOST_TEST(seqnum_2 == 43u);
BOOST_TEST(writer.done());
// Operation start for message 3
writer.prepare_write(mock_message{msg_3}, seqnum_3);
BOOST_TEST(!writer.done());
// Chunk 3
chunk = writer.current_chunk();
expected = create_frame(21, msg_3);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 3
writer.resume(6);
BOOST_TEST(seqnum_3 == 22u);
BOOST_TEST(writer.done());
}
// After a message has been partially written, we can
// prepare a write and resume operation normally. This can
// happen after a tiemout ellapses and a reconnection occurs.
BOOST_AUTO_TEST_CASE(message_half_written)
{
message_writer writer;
std::vector<std::uint8_t> msg_1{0x01, 0x02, 0x04};
std::vector<std::uint8_t> msg_2{0x04, 0x05, 0x06, 0x09, 0xff};
std::uint8_t seqnum_1 = 2;
std::uint8_t seqnum_2 = 42;
// Operation start for message 1
writer.prepare_write(mock_message{msg_1}, seqnum_1);
BOOST_TEST(!writer.done());
// Chunk 1
auto chunk = writer.current_chunk();
auto expected = create_frame(2, msg_1);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// Remaining of message 1 is never written.
// Operation start for message 2
writer.prepare_write(mock_message{msg_2}, seqnum_2);
BOOST_TEST(!writer.done());
// Chunk 2
chunk = writer.current_chunk();
expected = create_frame(42, msg_2);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 2
writer.resume(9);
BOOST_TEST(seqnum_2 == 43u);
BOOST_TEST(writer.done());
}
BOOST_AUTO_TEST_CASE(pipelined_write)
{
message_writer writer;
std::vector<std::uint8_t> msg_1{0x01, 0x02, 0x04, 0x05};
std::vector<std::uint8_t> msg_2{0x04, 0x05, 0x06, 0x09, 0xff};
std::uint8_t seqnum_1 = 2;
std::uint8_t seqnum_2 = 42;
// Prepare a pipelined write. This is currently limited
// to two messages which are shorter than the max frame size.
writer.prepare_pipelined_write(mock_message{msg_1}, seqnum_1, mock_message{msg_2}, seqnum_2);
BOOST_TEST(!writer.done());
// Chunk 1
auto chunk = writer.current_chunk();
auto expected = buffer_builder().add(create_frame(2, msg_1)).add(create_frame(42, msg_2)).build();
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// Some bytes acknowledged
writer.resume(5);
chunk = writer.current_chunk();
auto expected_view = span<std::uint8_t>(expected).subspan(5);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected_view);
BOOST_TEST(!writer.done());
// More bytes acknowledged
writer.resume(10);
chunk = writer.current_chunk();
expected_view = span<std::uint8_t>(expected).subspan(15);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected_view);
BOOST_TEST(!writer.done());
// Final bytes acknowledged
writer.resume(2);
BOOST_TEST(writer.done());
BOOST_TEST(seqnum_1 == 3u);
BOOST_TEST(seqnum_2 == 43u);
}
// Interleaving pipelined writes with regular writes works
BOOST_AUTO_TEST_CASE(pipelined_write_interleaved)
{
message_writer writer;
std::vector<std::uint8_t> msg_1{0x01, 0x02, 0x04};
std::vector<std::uint8_t> msg_2{0x04, 0x05, 0x06, 0x09, 0xff};
std::vector<std::uint8_t> msg_3{0x02, 0xab};
std::vector<std::uint8_t> msg_4{0x05};
std::uint8_t seqnum_1 = 2;
std::uint8_t seqnum_2 = 42;
std::uint8_t seqnum_3 = 21;
std::uint8_t seqnum_4 = 100;
// Operation start for message 1
writer.prepare_write(mock_message{msg_1}, seqnum_1);
BOOST_TEST(!writer.done());
// Chunk 1
auto chunk = writer.current_chunk();
auto expected = create_frame(2, msg_1);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 1
writer.resume(7);
BOOST_TEST(seqnum_1 == 3u);
BOOST_TEST(writer.done());
// Operation start for messages 2 and 3
writer.prepare_pipelined_write(mock_message{msg_2}, seqnum_2, mock_message{msg_3}, seqnum_3);
BOOST_TEST(!writer.done());
// Chunk 2
chunk = writer.current_chunk();
expected = concat_copy(create_frame(42, msg_2), create_frame(21, msg_3));
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful messages 2 and 3
writer.resume(15);
BOOST_TEST(seqnum_2 == 43u);
BOOST_TEST(seqnum_3 == 22u);
BOOST_TEST(writer.done());
// Operation start for message 4
writer.prepare_write(mock_message{msg_4}, seqnum_4);
BOOST_TEST(!writer.done());
// Chunk 4
chunk = writer.current_chunk();
expected = create_frame(100, msg_4);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 4
writer.resume(5);
BOOST_TEST(seqnum_4 == 101u);
BOOST_TEST(writer.done());
}
// Leaving a message half written is not a problem
BOOST_AUTO_TEST_CASE(pipelined_message_half_written)
{
message_writer writer;
std::vector<std::uint8_t> msg_1{0x01, 0x02, 0x04};
std::vector<std::uint8_t> msg_2{0x04, 0x05, 0x06, 0x09, 0xff};
std::vector<std::uint8_t> msg_3{0x02, 0xab};
std::uint8_t seqnum_1 = 2;
std::uint8_t seqnum_2 = 42;
std::uint8_t seqnum_3 = 21;
// Operation start for messages 1 and 2
writer.prepare_pipelined_write(mock_message{msg_1}, seqnum_1, mock_message{msg_2}, seqnum_2);
BOOST_TEST(!writer.done());
auto chunk = writer.current_chunk();
auto expected = concat_copy(create_frame(2, msg_1), create_frame(42, msg_2));
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// Part of this pipeline is written
writer.resume(8);
BOOST_TEST(!writer.done());
// The rest is never written
// Operation start for message 3
writer.prepare_write(mock_message{msg_3}, seqnum_3);
BOOST_TEST(!writer.done());
// Chunk
chunk = writer.current_chunk();
expected = create_frame(21, msg_3);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(chunk, expected);
// On write successful message 3
writer.resume(6);
BOOST_TEST(seqnum_3 == 22u);
BOOST_TEST(writer.done());
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace
+75 -27
View File
@@ -11,16 +11,13 @@
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/ping.hpp>
#include <boost/mysql/impl/internal/sansio/run_pipeline.hpp>
#include <boost/test/unit_test.hpp>
#include <cstddef>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/printing.hpp"
#include "test_unit/algo_test.hpp"
#include "test_unit/create_err.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_ok.hpp"
#include "test_unit/create_ok_frame.hpp"
@@ -29,15 +26,81 @@ using namespace boost::mysql;
BOOST_AUTO_TEST_SUITE(test_ping)
struct fixture : algo_fixture_base
//
// read_ping_response_algo
//
struct read_response_fixture : algo_fixture_base
{
detail::ping_algo algo{st, {&diag}};
detail::read_ping_response_algo algo{&diag, 57};
// Clearing diagnostics is not this algorithm's responsibility
read_response_fixture() : algo_fixture_base(diagnostics()) {}
};
BOOST_AUTO_TEST_CASE(success)
BOOST_AUTO_TEST_CASE(read_response_success)
{
// Setup
fixture fix;
read_response_fixture fix;
// Run the test
algo_test()
.expect_read(create_ok_frame(57, ok_builder().build())) // OK response
.check(fix);
// The OK packet was processed correctly
BOOST_TEST(fix.st.backslash_escapes);
}
BOOST_AUTO_TEST_CASE(read_response_success_no_backslash_escapes)
{
// Setup
read_response_fixture fix;
// Run the test
algo_test()
.expect_read(create_ok_frame(57, ok_builder().no_backslash_escapes(true).build())) // OK response
.check(fix);
// The OK packet was processed correctly
BOOST_TEST(!fix.st.backslash_escapes);
}
BOOST_AUTO_TEST_CASE(read_response_error_network)
{
algo_test()
.expect_read(create_ok_frame(57, ok_builder().build()))
.check_network_errors<read_response_fixture>();
}
BOOST_AUTO_TEST_CASE(read_response_error_packet)
{
// Setup
read_response_fixture fix;
// Run the test
algo_test()
.expect_read(err_builder()
.seqnum(57)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame()) // Error response
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
}
//
// setup_ping_pipeline: running a pipeline with these parameters
// has the intended effect
//
struct ping_fixture : algo_fixture_base
{
detail::run_pipeline_algo algo{detail::setup_ping_pipeline(st, {&diag})};
};
BOOST_AUTO_TEST_CASE(ping_success)
{
// Setup
ping_fixture fix;
// Run the test
algo_test()
@@ -49,34 +112,19 @@ BOOST_AUTO_TEST_CASE(success)
BOOST_TEST(fix.st.backslash_escapes);
}
BOOST_AUTO_TEST_CASE(success_no_backslash_escapes)
{
// Setup
fixture fix;
// Run the test
algo_test()
.expect_write({0x01, 0x00, 0x00, 0x00, 0x0e}) // ping request
.expect_read(create_ok_frame(1, ok_builder().no_backslash_escapes(true).build())) // OK response
.check(fix);
// The OK packet was processed correctly
BOOST_TEST(!fix.st.backslash_escapes);
}
BOOST_AUTO_TEST_CASE(error_network)
BOOST_AUTO_TEST_CASE(ping_error_network)
{
// Check for net errors for each read/write
algo_test()
.expect_write({0x01, 0x00, 0x00, 0x00, 0x0e})
.expect_read(create_ok_frame(1, ok_builder().build()))
.check_network_errors<fixture>();
.check_network_errors<ping_fixture>();
}
BOOST_AUTO_TEST_CASE(error_response)
BOOST_AUTO_TEST_CASE(ping_error_response)
{
// Setup
fixture fix;
ping_fixture fix;
// Run the test
algo_test()
+216
View File
@@ -0,0 +1,216 @@
//
// 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/statement.hpp>
#include <boost/mysql/impl/internal/protocol/impl/protocol_types.hpp>
#include <boost/mysql/impl/internal/protocol/impl/serialization_context.hpp>
#include <boost/mysql/impl/internal/sansio/prepare_statement.hpp>
#include <boost/test/unit_test.hpp>
#include <cstdint>
#include <vector>
#include "test_unit/algo_test.hpp"
#include "test_unit/create_coldef_frame.hpp"
#include "test_unit/create_err.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_meta.hpp"
#include "test_unit/create_prepare_statement_response.hpp"
#include "test_unit/create_query_frame.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
BOOST_AUTO_TEST_SUITE(test_prepare_statement)
//
// read_prepare_statement_response_algo
//
struct read_response_fixture : algo_fixture_base
{
detail::read_prepare_statement_response_algo algo{&diag, 19};
// Clearing diagnostics is not this algorithm's responsibility
read_response_fixture() : algo_fixture_base(diagnostics()) {}
statement result() const { return algo.result(st); }
};
BOOST_AUTO_TEST_CASE(read_response_success)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(1).num_columns(1).num_params(2).build())
.expect_read(create_coldef_frame(20, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(21, meta_builder().name("other").build_coldef()))
.expect_read(create_coldef_frame(22, meta_builder().name("final").build_coldef()))
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 1u);
BOOST_TEST(stmt.num_params() == 2u);
}
BOOST_AUTO_TEST_CASE(read_response_success_0cols)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(5).num_columns(0).num_params(1).build())
.expect_read(create_coldef_frame(20, meta_builder().name("abc").build_coldef()))
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 5u);
BOOST_TEST(stmt.num_params() == 1u);
}
BOOST_AUTO_TEST_CASE(read_response_success_0params)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(214).num_columns(2).num_params(0).build())
.expect_read(create_coldef_frame(20, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(21, meta_builder().name("defff").build_coldef()))
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 214u);
BOOST_TEST(stmt.num_params() == 0u);
}
BOOST_AUTO_TEST_CASE(read_response_success_0cols_0params)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(98).num_columns(0).num_params(0).build())
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 98u);
BOOST_TEST(stmt.num_params() == 0u);
}
// A statement_id == 0 doesn't cause trouble
BOOST_AUTO_TEST_CASE(read_response_success_0id)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(0).num_columns(0).num_params(1).build())
.expect_read(create_coldef_frame(20, meta_builder().name("abc").build_coldef()))
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 0u);
BOOST_TEST(stmt.num_params() == 1u);
}
BOOST_AUTO_TEST_CASE(read_response_error_network)
{
algo_test()
.expect_read(prepare_stmt_response_builder().seqnum(19).id(1).num_columns(1).num_params(2).build())
.expect_read(create_coldef_frame(20, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(21, meta_builder().name("other").build_coldef()))
.expect_read(create_coldef_frame(22, meta_builder().name("final").build_coldef()))
.check_network_errors<read_response_fixture>();
}
BOOST_AUTO_TEST_CASE(read_response_error_packet)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test()
.expect_read(err_builder()
.seqnum(19)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
}
//
// prepare_statement_algo
//
struct prepare_fixture : algo_fixture_base
{
detail::prepare_statement_algo algo{
{&diag, "SELECT 1"}
};
statement result() const { return algo.result(st); }
};
BOOST_AUTO_TEST_CASE(prepare_success)
{
// Setup
prepare_fixture fix;
// Run the algo
algo_test()
.expect_write(create_prepare_statement_frame(0, "SELECT 1"))
.expect_read(prepare_stmt_response_builder().seqnum(1).id(29).num_columns(0).num_params(2).build())
.expect_read(create_coldef_frame(2, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(3, meta_builder().name("other").build_coldef()))
.check(fix);
// The statement was created successfully
auto stmt = fix.result();
BOOST_TEST(stmt.id() == 29u);
BOOST_TEST(stmt.num_params() == 2u);
}
BOOST_AUTO_TEST_CASE(prepare_network_error)
{
// This covers errors in the request and the response
algo_test()
.expect_write(create_prepare_statement_frame(0, "SELECT 1"))
.expect_read(prepare_stmt_response_builder().seqnum(1).id(29).num_columns(0).num_params(1).build())
.expect_read(create_coldef_frame(2, meta_builder().name("abc").build_coldef()))
.check_network_errors<prepare_fixture>();
}
// Spotcheck: an error while reading the response is propagated correctly
BOOST_AUTO_TEST_CASE(prepare_error_packet)
{
// Setup
prepare_fixture fix;
// Run the algo
algo_test()
.expect_write(create_prepare_statement_frame(0, "SELECT 1"))
.expect_read(err_builder()
.seqnum(1)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
}
BOOST_AUTO_TEST_SUITE_END()
+21 -1
View File
@@ -39,7 +39,6 @@ struct fixture : algo_fixture_base
{
mock_execution_processor proc;
detail::read_resultset_head_algo algo{
st,
{&diag, &proc}
};
@@ -255,4 +254,25 @@ BOOST_AUTO_TEST_CASE(error_on_meta)
fix.proc.num_calls().on_num_meta(1).on_meta(1).validate();
}
BOOST_AUTO_TEST_CASE(reset)
{
// Setup
fixture fix;
// Run the algo once
algo_test()
.expect_read(create_frame(1, {0x01})) // 1 metadata follows
.expect_read(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef()))
.check(fix);
fix.proc.num_calls().on_num_meta(1).on_meta(1).validate();
// Reset. Place the processor into a state where we can read head again
fix.algo.reset();
add_ok(fix.proc, ok_builder().more_results(true).build());
// Run it again
algo_test().expect_read(create_ok_frame(3, ok_builder().build())).check(fix);
fix.proc.num_calls().on_num_meta(1).on_meta(1).on_row_ok_packet(1).on_head_ok_packet(1).validate();
}
BOOST_AUTO_TEST_SUITE_END()
+52 -12
View File
@@ -43,7 +43,6 @@ struct fixture : algo_fixture_base
mock_execution_processor proc;
std::array<row1, 3> storage;
detail::read_some_rows_algo algo{
st,
{&diag, &proc, ref()}
};
@@ -66,6 +65,8 @@ struct fixture : algo_fixture_base
for (std::size_t i = 0; i < num_rows; ++i)
BOOST_TEST(proc.refs()[i].offset() == i);
}
std::size_t result() const { return algo.result(st); }
};
BOOST_AUTO_TEST_CASE(eof)
@@ -80,7 +81,7 @@ BOOST_AUTO_TEST_CASE(eof)
)
.check(fix);
BOOST_TEST(fix.algo.result() == 0u); // num read rows
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_reading_head());
BOOST_TEST(fix.proc.affected_rows() == 1u);
BOOST_TEST(fix.proc.info() == "1st");
@@ -97,7 +98,7 @@ BOOST_AUTO_TEST_CASE(eof_no_backslash_escapes)
.expect_read(create_eof_frame(42, ok_builder().no_backslash_escapes(true).more_results(true).build()))
.check(fix);
BOOST_TEST(fix.algo.result() == 0u); // num read rows
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_reading_head());
BOOST_TEST(!fix.st.backslash_escapes);
}
@@ -116,7 +117,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows)
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 2u); // num read rows
BOOST_TEST(fix.result() == 2u); // num read rows
BOOST_TEST(fix.proc.is_reading_rows());
fix.validate_refs(2);
fix.proc.num_calls()
@@ -147,7 +148,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows_eof)
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 2u); // num read rows
BOOST_TEST(fix.result() == 2u); // num read rows
BOOST_TEST_REQUIRE(fix.proc.is_reading_head());
BOOST_TEST(fix.proc.affected_rows() == 1u);
BOOST_TEST(fix.proc.info() == "1st");
@@ -182,7 +183,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows_eof_multiresult)
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 1u); // num read rows
BOOST_TEST(fix.result() == 1u); // num read rows
BOOST_TEST_REQUIRE(fix.proc.is_reading_head());
BOOST_TEST(fix.proc.affected_rows() == 1u);
BOOST_TEST(fix.proc.info() == "1st");
@@ -214,7 +215,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows_out_of_span_space)
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 3u); // num read rows
BOOST_TEST(fix.result() == 3u); // num read rows
fix.validate_refs(3);
BOOST_TEST(fix.proc.is_reading_rows());
fix.proc.num_calls()
@@ -242,7 +243,7 @@ BOOST_AUTO_TEST_CASE(successive_calls_keep_parsing_state)
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 2u); // num read rows
BOOST_TEST(fix.result() == 2u); // num read rows
fix.validate_refs(2);
BOOST_TEST(fix.proc.is_reading_rows());
fix.proc.num_calls()
@@ -254,14 +255,14 @@ BOOST_AUTO_TEST_CASE(successive_calls_keep_parsing_state)
.validate();
// Run the algo again
fix.algo = detail::read_some_rows_algo(fix.st, fix.algo.params());
fix.algo.reset();
fix.diag = create_server_diag("Diagnostics not cleared");
algo_test()
.expect_read(buffer_builder().add(boost::span<const std::uint8_t>(eof).subspan(6)).build())
.check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 0u); // num read rows
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_complete());
fix.proc.num_calls()
.on_num_meta(1)
@@ -286,7 +287,7 @@ BOOST_AUTO_TEST_CASE(state_complete)
algo_test().check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 0u); // num read rows
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_complete());
fix.proc.num_calls().on_num_meta(1).on_meta(1).on_row_ok_packet(1).validate();
}
@@ -301,7 +302,7 @@ BOOST_AUTO_TEST_CASE(state_reading_head)
algo_test().check(fix);
// Validate
BOOST_TEST(fix.algo.result() == 0u); // num read rows
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_reading_head());
fix.proc.num_calls().on_num_meta(1).on_meta(1).on_row_ok_packet(1).validate();
}
@@ -376,4 +377,43 @@ BOOST_AUTO_TEST_CASE(error_deserialize_row_message)
fix.proc.num_calls().on_num_meta(1).on_meta(1).on_row_batch_start(1).validate();
}
BOOST_AUTO_TEST_CASE(reset)
{
// Setup
fixture fix;
// Run the algo. Read a row
algo_test().expect_read(create_text_row_message(42, "abc")).check(fix);
// Validate
BOOST_TEST(fix.result() == 1u); // num read rows
BOOST_TEST(fix.proc.is_reading_rows());
fix.validate_refs(1);
fix.proc.num_calls()
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(1)
.on_row(1)
.on_row_batch_finish(1)
.validate();
// Reset
fix.algo.reset();
// Run the algo again. Read an OK packet
algo_test().expect_read(create_eof_frame(43, ok_builder().build())).check(fix);
// Check
BOOST_TEST(fix.result() == 0u); // num read rows
BOOST_TEST(fix.proc.is_complete());
fix.proc.num_calls()
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(2)
.on_row(1)
.on_row_batch_finish(2)
.on_row_ok_packet(1)
.validate();
}
BOOST_AUTO_TEST_SUITE_END()
@@ -7,6 +7,7 @@
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
@@ -34,7 +35,6 @@ struct fixture : algo_fixture_base
{
execution_state_impl exec_st;
detail::read_some_rows_dynamic_algo algo{
st,
{&diag, &exec_st}
};
@@ -47,6 +47,8 @@ struct fixture : algo_fixture_base
// Put something in shared_fields, simulating a previous read
st.shared_fields.push_back(field_view("prev"));
}
rows_view result() const { return algo.result(st); }
};
BOOST_AUTO_TEST_CASE(eof)
@@ -59,7 +61,7 @@ BOOST_AUTO_TEST_CASE(eof)
.expect_read(create_eof_frame(42, ok_builder().affected_rows(1).info("1st").build()))
.check(fix);
BOOST_TEST(fix.algo.result() == makerows(1));
BOOST_TEST(fix.result() == makerows(1));
BOOST_TEST_REQUIRE(fix.exec_st.is_complete());
BOOST_TEST(fix.exec_st.get_affected_rows() == 1u);
BOOST_TEST(fix.exec_st.get_info() == "1st");
@@ -79,7 +81,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows)
.check(fix);
// Check
BOOST_TEST(fix.algo.result() == makerows(1, "abc", "von"));
BOOST_TEST(fix.result() == makerows(1, "abc", "von"));
BOOST_TEST(fix.exec_st.is_reading_rows());
}
@@ -98,7 +100,7 @@ BOOST_AUTO_TEST_CASE(batch_with_rows_eof)
.check(fix);
// Check
BOOST_TEST(fix.algo.result() == makerows(1, "abc", "von"));
BOOST_TEST(fix.result() == makerows(1, "abc", "von"));
BOOST_TEST_REQUIRE(fix.exec_st.is_complete());
BOOST_TEST(fix.exec_st.get_affected_rows() == 1u);
BOOST_TEST(fix.exec_st.get_info() == "1st");
+76 -138
View File
@@ -12,35 +12,100 @@
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/impl/internal/sansio/reset_connection.hpp>
#include <boost/mysql/impl/internal/sansio/run_pipeline.hpp>
#include <boost/test/unit_test.hpp>
#include "test_common/assert_buffer_equals.hpp"
#include "test_common/buffer_concat.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_unit/algo_test.hpp"
#include "test_unit/create_err.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_ok.hpp"
#include "test_unit/create_ok_frame.hpp"
#include "test_unit/create_query_frame.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
BOOST_AUTO_TEST_SUITE(test_reset_connection)
struct fixture : algo_fixture_base
//
// read_reset_connection_response_algo
//
struct read_response_fixture : algo_fixture_base
{
detail::reset_connection_algo algo;
detail::read_reset_connection_response_algo algo{&diag, 11};
fixture(character_set charset = {}) : algo(st, {&diag, charset}) {}
// Clearing diagnostics is not this algorithm's responsibility
read_response_fixture() : algo_fixture_base(diagnostics()) {}
};
BOOST_AUTO_TEST_CASE(read_response_success)
{
// Setup
read_response_fixture fix;
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test().expect_read(create_ok_frame(11, ok_builder().build())).check(fix);
// The OK packet was processed correctly. The charset was reset
BOOST_TEST(fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(read_response_success_no_backslash_escapes)
{
// Setup
read_response_fixture fix;
// Run the algo
algo_test().expect_read(create_ok_frame(11, ok_builder().no_backslash_escapes(true).build())).check(fix);
// The OK packet was processed correctly.
// It's OK to run this algo without a known charset
BOOST_TEST(!fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(read_response_error_network)
{
algo_test()
.expect_read(create_ok_frame(11, ok_builder().build()))
.check_network_errors<read_response_fixture>();
}
BOOST_AUTO_TEST_CASE(read_response_error_packet)
{
// Setup
read_response_fixture fix;
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test()
.expect_read(err_builder()
.seqnum(11)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
// The charset was not updated
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
//
// setup_reset_connection_pipeline: running a pipeline with these parameters
// has the intended effect
//
struct reset_conn_fixture : algo_fixture_base
{
detail::run_pipeline_algo algo{detail::setup_reset_connection_pipeline(st, {&diag})};
};
BOOST_AUTO_TEST_CASE(success)
{
// Setup
fixture fix;
reset_conn_fixture fix;
fix.st.current_charset = utf8mb4_charset;
// Run the algo
@@ -54,36 +119,19 @@ BOOST_AUTO_TEST_CASE(success)
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(success_no_backslash_escapes)
{
// Setup
fixture fix;
// Run the algo
algo_test()
.expect_write(create_frame(0, {0x1f}))
.expect_read(create_ok_frame(1, ok_builder().no_backslash_escapes(true).build()))
.check(fix);
// The OK packet was processed correctly.
// It's OK to run this algo without a known charset
BOOST_TEST(!fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(error_network)
BOOST_AUTO_TEST_CASE(reset_conn_error_network)
{
// This covers errors in read and write
algo_test()
.expect_write(create_frame(0, {0x1f}))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check_network_errors<fixture>();
.check_network_errors<reset_conn_fixture>();
}
BOOST_AUTO_TEST_CASE(error_response)
BOOST_AUTO_TEST_CASE(reset_conn_error_response)
{
// Setup
fixture fix;
reset_conn_fixture fix;
fix.st.current_charset = utf8mb4_charset;
// Run the algo
@@ -100,114 +148,4 @@ BOOST_AUTO_TEST_CASE(error_response)
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
BOOST_AUTO_TEST_CASE(charset_success)
{
// Setup
fixture fix(ascii_charset);
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test()
.expect_write(concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SET NAMES 'ascii'")))
.expect_read(create_ok_frame(1, ok_builder().build()))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check(fix);
// The OK packets were processed correctly. The charset is set
BOOST_TEST(fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr()->name == "ascii");
}
BOOST_AUTO_TEST_CASE(charset_error_network)
{
struct charset_fixture : fixture
{
charset_fixture() : fixture(ascii_charset) {}
};
// This covers errors in read and write
algo_test()
.expect_write(concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SET NAMES 'ascii'")))
.expect_read(create_ok_frame(1, ok_builder().build()))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check_network_errors<charset_fixture>();
}
BOOST_AUTO_TEST_CASE(charset_reset_error_response)
{
// Setup
fixture fix(ascii_charset);
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test()
.expect_write(concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SET NAMES 'ascii'")))
.expect_read(
err_builder().seqnum(1).code(common_server_errc::er_cant_create_db).message("abc").build_frame()
)
.expect_read(create_ok_frame(1, ok_builder().build()))
.check(fix, common_server_errc::er_cant_create_db, create_server_diag("abc"));
// Both packets are read, even if the first one is an error. The character set is updated because the
// second packet is an OK
BOOST_TEST(fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr()->name == "ascii");
}
BOOST_AUTO_TEST_CASE(charset_set_names_error_response)
{
// Setup
fixture fix(ascii_charset);
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test()
.expect_write(concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SET NAMES 'ascii'")))
.expect_read(create_ok_frame(1, ok_builder().build()))
.expect_read(
err_builder().seqnum(1).code(common_server_errc::er_cant_create_db).message("abc").build_frame()
)
.check(fix, common_server_errc::er_cant_create_db, create_server_diag("abc"));
// Both packets are read. The character set is reset
BOOST_TEST(fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(charset_all_error_responses)
{
// Setup
fixture fix(ascii_charset);
fix.st.current_charset = utf8mb4_charset;
// Run the algo
algo_test()
.expect_write(concat_copy(create_frame(0, {0x1f}), create_query_frame(0, "SET NAMES 'ascii'")))
.expect_read(err_builder()
.seqnum(1)
.code(common_server_errc::er_autoinc_read_failed)
.message("hello")
.build_frame())
.expect_read(
err_builder().seqnum(1).code(common_server_errc::er_cant_create_db).message("abc").build_frame()
)
.check(fix, common_server_errc::er_autoinc_read_failed, create_server_diag("hello"));
// Both packets are read. The character is not reset
BOOST_TEST(fix.st.backslash_escapes);
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
BOOST_AUTO_TEST_CASE(charset_error_nonascii_charset_name)
{
// Setup
fixture fix({"lat\xc3\xadn", ascii_charset.next_char});
// Run the algo
algo_test().check(fix, client_errc::invalid_encoding);
// The current character set was not updated
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_SUITE_END()
+603
View File
@@ -0,0 +1,603 @@
//
// 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/character_set.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/error_with_diagnostics.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/algo_params.hpp>
#include <boost/mysql/detail/pipeline.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/impl/internal/sansio/run_pipeline.hpp>
#include <boost/asio/error.hpp>
#include <boost/core/span.hpp>
#include <boost/test/unit_test.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "test_common/buffer_concat.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_unit/algo_test.hpp"
#include "test_unit/create_coldef_frame.hpp"
#include "test_unit/create_err.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_meta.hpp"
#include "test_unit/create_ok.hpp"
#include "test_unit/create_ok_frame.hpp"
#include "test_unit/create_prepare_statement_response.hpp"
#include "test_unit/create_row_message.hpp"
#include "test_unit/mock_execution_processor.hpp"
#include "test_unit/printing.hpp"
using namespace boost::mysql::test;
using namespace boost::mysql;
namespace asio = boost::asio;
using boost::span;
using boost::test_tools::per_element;
using detail::pipeline_request_stage;
using detail::pipeline_stage_kind;
using detail::resultset_encoding;
namespace boost {
namespace mysql {
namespace test {
struct mock_pipeline_response
{
struct item
{
mock_execution_processor proc;
statement stmt;
errcode_with_diagnostics err;
};
std::size_t setup_num_calls{0u};
span<const detail::pipeline_request_stage> setup_args;
std::vector<item> items;
};
} // namespace test
namespace detail {
template <>
struct pipeline_response_traits<test::mock_pipeline_response>
{
using response_type = test::mock_pipeline_response;
static void setup(response_type& self, span<const pipeline_request_stage> request)
{
++self.setup_num_calls;
self.setup_args = request;
self.items.resize(request.size());
}
static execution_processor& get_processor(response_type& self, std::size_t idx)
{
// This function is only defined for execute stages
BOOST_TEST_REQUIRE(idx < self.items.size());
BOOST_TEST(self.setup_args[idx].kind == pipeline_stage_kind::execute);
return self.items[idx].proc;
}
static void set_result(response_type& self, std::size_t idx, statement stmt)
{
// This function is only defined for prepare statement stages
BOOST_TEST_REQUIRE(idx < self.items.size());
BOOST_TEST(self.setup_args[idx].kind == pipeline_stage_kind::prepare_statement);
self.items[idx].stmt = stmt;
}
static void set_error(response_type& self, std::size_t idx, error_code ec, diagnostics&& diag)
{
BOOST_TEST_REQUIRE(idx < self.items.size());
self.items[idx].err = {ec, std::move(diag)};
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
BOOST_AUTO_TEST_SUITE(test_run_pipeline)
const std::vector<std::uint8_t> mock_request{1, 2, 3, 4, 5, 6, 7, 9, 21};
struct fixture : algo_fixture_base
{
detail::run_pipeline_algo algo;
mock_pipeline_response resp;
fixture(span<const pipeline_request_stage> stages, span<const std::uint8_t> req_buffer = mock_request)
: algo({
&diag,
detail::pipeline_request_view{req_buffer, stages},
detail::pipeline_response_ref(resp)
})
{
}
// Verify that the stages we passed match the ones used in setup
void check_setup(span<const pipeline_request_stage> stages)
{
BOOST_TEST(resp.setup_num_calls == 1u);
BOOST_TEST(resp.setup_args == stages, per_element());
}
// Verify that all stages succeeded
void check_all_stages_succeeded()
{
for (const auto& item : resp.items)
BOOST_TEST(item.err == errcode_with_diagnostics{});
}
// Verify that a certain step failed
void check_stage_error(std::size_t i, const errcode_with_diagnostics& expected)
{
BOOST_TEST(resp.items.at(i).err == expected);
}
};
// All stage kinds work properly
BOOST_AUTO_TEST_CASE(execute_success)
{
// Setup. Each step has a different encoding
const std::array<pipeline_request_stage, 2> stages{
{
{pipeline_stage_kind::execute, 42u, resultset_encoding::binary},
{pipeline_stage_kind::execute, 11u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(create_ok_frame(42, ok_builder().info("1st").build())) // 1st op ok
.expect_read(create_frame(11, {0x01})) // 2nd op OK, 1 column
.expect_read(create_coldef_frame(12, meta_builder().type(column_type::tinyint).build_coldef()))
.expect_read(buffer_builder()
.add(create_text_row_message(13, 42))
.add(create_text_row_message(14, 43))
.add(create_eof_frame(15, ok_builder().info("2nd").build()))
.build())
.check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// Check each processor calls
auto& proc0 = fix.resp.items.at(0).proc;
proc0.num_calls().reset(1).on_head_ok_packet(1).validate();
BOOST_TEST(proc0.info() == "1st");
BOOST_TEST(proc0.encoding() == resultset_encoding::binary);
auto& proc1 = fix.resp.items.at(1).proc;
proc1.num_calls()
.reset(1)
.on_num_meta(1)
.on_meta(1)
.on_row_batch_start(1)
.on_row(2)
.on_row_batch_finish(1)
.on_row_ok_packet(1)
.validate();
BOOST_TEST(proc1.info() == "2nd");
}
BOOST_AUTO_TEST_CASE(prepare_statement_success)
{
// Setup
const std::array<pipeline_request_stage, 2> stages{
{
{pipeline_stage_kind::prepare_statement, 42u, {}},
{pipeline_stage_kind::prepare_statement, 11u, {}},
}
};
fixture fix(stages);
// Run the test. 1st statement has 2 meta, 2nd has 1
algo_test()
.expect_write(mock_request)
.expect_read(prepare_stmt_response_builder().seqnum(42).id(7).num_columns(0).num_params(2).build())
.expect_read(create_coldef_frame(43, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(44, meta_builder().name("def").build_coldef()))
.expect_read(prepare_stmt_response_builder().seqnum(11).id(9).num_columns(0).num_params(1).build())
.expect_read(create_coldef_frame(12, meta_builder().name("aaa").build_coldef()))
.check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// Check the resulting statements
auto stmt0 = fix.resp.items.at(0).stmt;
BOOST_TEST(stmt0.id() == 7u);
BOOST_TEST(stmt0.num_params() == 2u);
auto stmt1 = fix.resp.items.at(1).stmt;
BOOST_TEST(stmt1.id() == 9u);
BOOST_TEST(stmt1.num_params() == 1u);
}
BOOST_AUTO_TEST_CASE(close_statement_success)
{
// Setup
const std::array<pipeline_request_stage, 2> stages{
{
{pipeline_stage_kind::close_statement, 3u, {}},
{pipeline_stage_kind::close_statement, 8u, {}},
}
};
fixture fix(stages);
// Run the test. Close statement doesn't have a response
algo_test().expect_write(mock_request).check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
}
BOOST_AUTO_TEST_CASE(reset_connection)
{
// Setup
const std::array<pipeline_request_stage, 1> stages{
{
{pipeline_stage_kind::reset_connection, 3u, {}},
}
};
fixture fix(stages);
fix.st.current_charset = utf8mb4_charset;
// Run the test
algo_test().expect_write(mock_request).expect_read(create_ok_frame(3, ok_builder().build())).check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// The current character set was reset
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
BOOST_AUTO_TEST_CASE(set_character_set)
{
// Setup
const std::array<pipeline_request_stage, 1> stages{
{
{pipeline_stage_kind::set_character_set, 19u, utf8mb4_charset},
}
};
fixture fix(stages);
// Run the test
algo_test().expect_write(mock_request).expect_read(create_ok_frame(19, ok_builder().build())).check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// The current character set was set
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
BOOST_AUTO_TEST_CASE(ping)
{
// Setup
const std::array<pipeline_request_stage, 1> stages{
{
{pipeline_stage_kind::ping, 32u, {}},
}
};
fixture fix(stages);
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(create_ok_frame(32, ok_builder().no_backslash_escapes(true).build()))
.check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// The OK packet was processed successfully
BOOST_TEST(fix.st.backslash_escapes == false);
}
BOOST_AUTO_TEST_CASE(combination)
{
// Setup. Typical connection setup pipeline, where we reset, set names,
// set the time_zone and prepare some statements
const std::array<pipeline_request_stage, 5> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
{pipeline_stage_kind::prepare_statement, 0u, {}},
{pipeline_stage_kind::prepare_statement, 1u, {}},
}
};
fixture fix(stages);
fix.st.backslash_escapes = false;
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(create_ok_frame(32, ok_builder().build()))
.expect_read(create_ok_frame(16, ok_builder().build()))
.expect_read(create_ok_frame(10, ok_builder().build()))
.expect_read(prepare_stmt_response_builder().seqnum(0).id(3).num_columns(1).num_params(1).build())
.expect_read(create_coldef_frame(1, meta_builder().name("abc").build_coldef()))
.expect_read(create_coldef_frame(2, meta_builder().name("def").build_coldef()))
.expect_read(prepare_stmt_response_builder().seqnum(1).id(1).num_columns(0).num_params(0).build())
.check(fix);
// Setup was called correctly and all stages succeeded
fix.check_setup(stages);
fix.check_all_stages_succeeded();
// The pipeline had its intended effect
BOOST_TEST(fix.st.backslash_escapes == true);
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
BOOST_TEST(fix.resp.items.at(3).stmt.id() == 3u);
BOOST_TEST(fix.resp.items.at(4).stmt.id() == 1u);
}
BOOST_AUTO_TEST_CASE(no_requests)
{
// Setup
fixture fix({}, {});
// Run the test. We complete immediately
algo_test().check(fix);
// Setup was called correctly
fix.check_setup({});
}
BOOST_AUTO_TEST_CASE(error_writing_request)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test. No response reading is attempted
algo_test().expect_write(mock_request, asio::error::eof).check(fix, asio::error::eof);
// Setup was called correctly
fix.check_setup(stages);
// All requests were marked as failed
fix.check_stage_error(0, {asio::error::eof, {}});
fix.check_stage_error(1, {asio::error::eof, {}});
fix.check_stage_error(2, {asio::error::eof, {}});
}
BOOST_AUTO_TEST_CASE(nonfatal_errors)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::prepare_statement, 32u, {}},
{pipeline_stage_kind::prepare_statement, 16u, {}},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test. Steps 1 and 3 fail.
// The first error is the operation's result
algo_test()
.expect_write(mock_request)
.expect_read(err_builder()
.seqnum(32)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.expect_read(prepare_stmt_response_builder().seqnum(16).id(3).num_columns(0).num_params(0).build())
.expect_read(err_builder()
.seqnum(10)
.code(common_server_errc::er_bad_field_error)
.message("other_msg")
.build_frame())
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
// Setup was called correctly
fix.check_setup(stages);
// Stage errors
fix.check_stage_error(0, {common_server_errc::er_bad_db_error, create_server_diag("my_message")});
fix.check_stage_error(1, {error_code(), {}});
fix.check_stage_error(2, {common_server_errc::er_bad_field_error, create_server_diag("other_msg")});
// The operation that succeeded had its result set
BOOST_TEST(fix.resp.items.at(1).stmt.id() == 3u);
}
BOOST_AUTO_TEST_CASE(nonfatal_errors_middle)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::prepare_statement, 32u, {}},
{pipeline_stage_kind::prepare_statement, 16u, {}},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test. Steps 1 and 3 fail.
// The first error is the operation's result
algo_test()
.expect_write(mock_request)
.expect_read(prepare_stmt_response_builder().seqnum(32).id(3).num_columns(0).num_params(0).build())
.expect_read(err_builder()
.seqnum(16)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.expect_read(create_ok_frame(10, ok_builder().no_backslash_escapes(true).build()))
.check(fix, common_server_errc::er_bad_db_error, create_server_diag("my_message"));
// Setup was called correctly
fix.check_setup(stages);
// Stage errors
fix.check_stage_error(0, {});
fix.check_stage_error(1, {common_server_errc::er_bad_db_error, create_server_diag("my_message")});
fix.check_stage_error(2, {});
// We processed the OK packet correctly
BOOST_TEST(fix.st.backslash_escapes == false);
}
BOOST_AUTO_TEST_CASE(fatal_error_first)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test. Reading the first response fails, and we don't further reading
algo_test()
.expect_write(mock_request)
.expect_read(asio::error::network_reset)
.check(fix, asio::error::network_reset);
// Setup was called correctly
fix.check_setup(stages);
// All subsequent requests were marked as failed
fix.check_stage_error(0, {asio::error::network_reset, {}});
fix.check_stage_error(1, {asio::error::network_reset, {}});
fix.check_stage_error(2, {asio::error::network_reset, {}});
}
BOOST_AUTO_TEST_CASE(fatal_error_middle)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(create_ok_frame(32, ok_builder().build()))
.expect_read(asio::error::network_reset)
.check(fix, asio::error::network_reset);
// Setup was called correctly
fix.check_setup(stages);
// All subsequent requests were marked as failed
fix.check_stage_error(0, {});
fix.check_stage_error(1, {asio::error::network_reset, {}});
fix.check_stage_error(2, {asio::error::network_reset, {}});
}
// If there are fatal and non-fatal errors, the fatal one is the result of the operation
BOOST_AUTO_TEST_CASE(nonfatal_then_fatal_error)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(err_builder()
.seqnum(32)
.code(common_server_errc::er_bad_db_error)
.message("my_message")
.build_frame())
.expect_read(asio::error::already_connected)
.check(fix, asio::error::already_connected);
// Setup was called correctly
fix.check_setup(stages);
// Stage results
fix.check_stage_error(0, {common_server_errc::er_bad_db_error, create_server_diag("my_message")});
fix.check_stage_error(1, {asio::error::already_connected, {}});
fix.check_stage_error(2, {asio::error::already_connected, {}});
}
// Edge case: fatal error with non-empty diagnostics
BOOST_AUTO_TEST_CASE(fatal_error_with_diag)
{
// Setup
const std::array<pipeline_request_stage, 3> stages{
{
{pipeline_stage_kind::reset_connection, 32u, {}},
{pipeline_stage_kind::set_character_set, 16u, utf8mb4_charset},
{pipeline_stage_kind::execute, 10u, resultset_encoding::text},
}
};
fixture fix(stages);
// Run the test
algo_test()
.expect_write(mock_request)
.expect_read(
err_builder().seqnum(32).code(common_server_errc::er_bad_db_error).message("bad db").build_frame()
)
.expect_read(err_builder()
.seqnum(16)
.code(common_server_errc::er_aborting_connection)
.message("aborting connection")
.build_frame())
.check(fix, common_server_errc::er_aborting_connection, create_server_diag("aborting connection"));
// Setup was called correctly
fix.check_setup(stages);
// Stage results
fix.check_stage_error(0, {common_server_errc::er_bad_db_error, create_server_diag("bad db")});
fix.check_stage_error(
1,
{common_server_errc::er_aborting_connection, create_server_diag("aborting connection")}
);
fix.check_stage_error(
2,
{common_server_errc::er_aborting_connection, create_server_diag("aborting connection")}
);
}
BOOST_AUTO_TEST_SUITE_END()
+101 -34
View File
@@ -17,12 +17,10 @@
#include <boost/core/span.hpp>
#include <boost/test/unit_test.hpp>
#include <cstddef>
#include "test_common/create_basic.hpp"
#include "test_common/create_diagnostics.hpp"
#include "test_unit/algo_test.hpp"
#include "test_unit/create_err.hpp"
#include "test_unit/create_frame.hpp"
#include "test_unit/create_ok.hpp"
#include "test_unit/create_ok_frame.hpp"
#include "test_unit/create_query_frame.hpp"
@@ -32,63 +30,100 @@ using namespace boost::mysql;
BOOST_AUTO_TEST_SUITE(test_set_character_set)
struct fixture : algo_fixture_base
//
// compose_set_names
//
BOOST_AUTO_TEST_CASE(compose_set_names_success)
{
detail::set_character_set_algo algo;
BOOST_TEST(detail::compose_set_names(utf8mb4_charset).value() == "SET NAMES 'utf8mb4'");
BOOST_TEST(detail::compose_set_names(ascii_charset).value() == "SET NAMES 'ascii'");
}
fixture(const character_set& charset = utf8mb4_charset) : algo(st, {&diag, charset}) {}
BOOST_AUTO_TEST_CASE(compose_set_names_needs_escaping)
{
// We don't create vulnerabilities when creating SET NAMES statements
character_set mock_charset{"ab'cd\"e", utf8mb4_charset.next_char};
BOOST_TEST(detail::compose_set_names(mock_charset).value() == "SET NAMES 'ab\\'cd\\\"e'");
}
BOOST_AUTO_TEST_CASE(compose_set_names_error)
{
struct
{
string_view name;
string_view charset_name;
} test_cases[] = {
{"utf8", "test-\xc3\xb1" }, // anything non-ascii is rejected
{"non_utf8", "test-\xb1-abc" },
{"null_character", makesv("with\0null")}, // NULL characters not allowed
};
for (const auto& tc : test_cases)
{
BOOST_TEST_CONTEXT(tc.charset_name)
{
character_set charset{tc.charset_name, utf8mb4_charset.next_char};
BOOST_TEST(detail::compose_set_names(charset).error() == client_errc::invalid_encoding);
}
}
}
//
// read_set_character_set_response_algo
//
struct read_response_fixture : algo_fixture_base
{
detail::read_set_character_set_response_algo algo;
// Clearing diagnostics is not this algorithm's responsibility
read_response_fixture(character_set charset = utf8mb4_charset)
: algo_fixture_base(diagnostics()), algo({&diag, charset, 29})
{
}
};
BOOST_AUTO_TEST_CASE(success)
BOOST_AUTO_TEST_CASE(read_response_success)
{
// Setup
fixture fix;
read_response_fixture fix;
// Run the algo
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check(fix);
algo_test().expect_read(create_ok_frame(29, ok_builder().build())).check(fix);
// The charset was updated
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
BOOST_AUTO_TEST_CASE(success_previous_charset)
BOOST_AUTO_TEST_CASE(read_response_success_previous_charset)
{
// Setup
fixture fix;
read_response_fixture fix;
fix.st.current_charset = ascii_charset;
// Run the algo
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check(fix);
algo_test().expect_read(create_ok_frame(29, ok_builder().build())).check(fix);
// The charset was updated
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
BOOST_AUTO_TEST_CASE(error_network)
BOOST_AUTO_TEST_CASE(read_response_error_network)
{
// This covers errors in read and write
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check_network_errors<fixture>();
.expect_read(create_ok_frame(29, ok_builder().build()))
.check_network_errors<read_response_fixture>();
}
BOOST_AUTO_TEST_CASE(error_response)
BOOST_AUTO_TEST_CASE(read_response_error_packet)
{
// Setup
fixture fix;
read_response_fixture fix;
// Run the algo
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(err_builder()
.seqnum(1)
.seqnum(29)
.code(common_server_errc::er_unknown_character_set)
.message("Unknown charset")
.build_frame())
@@ -98,14 +133,36 @@ BOOST_AUTO_TEST_CASE(error_response)
BOOST_TEST(fix.st.charset_ptr() == nullptr);
}
// Character set function that always returns 1
static std::size_t stub_next_char(boost::span<const unsigned char>) noexcept { return 1u; }
//
// set_character_set_algo
//
struct set_charset_fixture : algo_fixture_base
{
detail::set_character_set_algo algo;
// Ensure we don't create vulnerabilities when composing SET NAMES
BOOST_AUTO_TEST_CASE(charset_name_needs_escaping)
set_charset_fixture(character_set charset = utf8mb4_charset) : algo({&diag, charset}) {}
};
BOOST_AUTO_TEST_CASE(set_charset_success)
{
// Setup
fixture fix({"lat'in\\", stub_next_char});
set_charset_fixture fix;
// Run the algo
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check(fix);
// The charset was updated
BOOST_TEST(fix.st.charset_ptr()->name == "utf8mb4");
}
// Ensure we don't create vulnerabilities when composing SET NAMES
BOOST_AUTO_TEST_CASE(set_charset_name_needs_escaping)
{
// Setup
set_charset_fixture fix({"lat'in\\", utf8mb4_charset.next_char});
// Run the algo
algo_test()
@@ -114,16 +171,26 @@ BOOST_AUTO_TEST_CASE(charset_name_needs_escaping)
.check(fix);
}
BOOST_AUTO_TEST_CASE(error_nonascii_charset_name)
BOOST_AUTO_TEST_CASE(set_charset_error_composing_request)
{
// Setup
fixture fix({"lat\xc3\xadn", stub_next_char});
set_charset_fixture fix({"lat\xc3\xadn", utf8mb4_charset.next_char});
fix.st.current_charset = ascii_charset;
// Run the algo
algo_test().check(fix, client_errc::invalid_encoding);
// The current character set was not updated
BOOST_TEST(fix.st.charset_ptr() == nullptr);
BOOST_TEST(fix.st.charset_ptr()->name == "ascii");
}
BOOST_AUTO_TEST_CASE(set_charset_error_network)
{
// This covers errors in reading the response
algo_test()
.expect_write(create_query_frame(0, "SET NAMES 'utf8mb4'"))
.expect_read(create_ok_frame(1, ok_builder().build()))
.check_network_errors<set_charset_fixture>();
}
BOOST_AUTO_TEST_SUITE_END()
+1 -1
View File
@@ -42,7 +42,7 @@ struct fixture : algo_fixture_base
mock_execution_processor proc;
detail::start_execution_algo algo;
fixture(any_execution_request req) : algo(st, {&diag, req, &proc}) {}
fixture(any_execution_request req) : algo({&diag, req, &proc}) {}
};
BOOST_AUTO_TEST_CASE(text_query)
+53 -80
View File
@@ -12,7 +12,6 @@
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
#include <boost/mysql/impl/internal/sansio/message_reader.hpp>
#include <boost/mysql/impl/internal/sansio/sansio_algorithm.hpp>
#include <boost/mysql/impl/internal/sansio/top_level_algo.hpp>
#include <boost/asio/coroutine.hpp>
@@ -53,26 +52,24 @@ const u8vec msg2(50, 0x04);
BOOST_AUTO_TEST_CASE(read_cached)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return read(seqnum);
BOOST_ASIO_CORO_YIELD return st.read(seqnum);
BOOST_TEST(ec == error_code());
BOOST_TEST(seqnum == 1u);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st_->reader.message(), msg1);
BOOST_ASIO_CORO_YIELD return read(seqnum);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st.reader.message(), msg1);
BOOST_ASIO_CORO_YIELD return st.read(seqnum);
BOOST_TEST(ec == error_code());
BOOST_TEST(seqnum == 2u);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st_->reader.message(), msg2);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st.reader.message(), msg2);
}
return next_action();
}
@@ -83,7 +80,7 @@ BOOST_AUTO_TEST_CASE(read_cached)
// Initial run yields a read request. We don't have cached data, so run_op returns it
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
BOOST_TEST(act.read_args().buffer.data() == st.reader.buffer().data());
BOOST_TEST(act.read_args().buffer.size() == st.reader.buffer().size());
BOOST_TEST(!act.read_args().use_ssl);
@@ -99,22 +96,20 @@ BOOST_AUTO_TEST_CASE(read_cached)
BOOST_AUTO_TEST_CASE(read_short_and_buffer_resizing)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return read(seqnum);
BOOST_ASIO_CORO_YIELD return st.read(seqnum);
BOOST_TEST(ec == error_code());
BOOST_TEST(seqnum == 1u);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st_->reader.message(), msg2);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(st.reader.message(), msg2);
}
return next_action();
}
@@ -125,7 +120,7 @@ BOOST_AUTO_TEST_CASE(read_short_and_buffer_resizing)
// Initial run yields a read request and resizes the buffer aprorpiately
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
BOOST_TEST(act.read_args().buffer.data() == st.reader.buffer().data());
BOOST_TEST(act.read_args().buffer.size() == st.reader.buffer().size());
BOOST_TEST(!act.read_args().use_ssl);
@@ -136,12 +131,12 @@ BOOST_AUTO_TEST_CASE(read_short_and_buffer_resizing)
act = algo.resume(error_code(), 4);
// The read request wasn't completely satisified, so more bytes are asked for
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
// Read part of the body
transfer(act.read_args().buffer, span<const std::uint8_t>(bytes.data() + 4, 10));
act = algo.resume(error_code(), 10);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
// Complete
transfer(act.read_args().buffer, span<const std::uint8_t>(bytes.data() + 14, bytes.size() - 14));
@@ -151,19 +146,17 @@ BOOST_AUTO_TEST_CASE(read_short_and_buffer_resizing)
BOOST_AUTO_TEST_CASE(read_parsing_error)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{42u};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return read(seqnum);
BOOST_ASIO_CORO_YIELD return st.read(seqnum);
BOOST_TEST(ec == error_code(client_errc::sequence_number_mismatch));
}
return next_action();
@@ -175,7 +168,7 @@ BOOST_AUTO_TEST_CASE(read_parsing_error)
// Initial run yields a read request. We don't have cached data, so run_op returns it
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
// Acknowledge the read request. This causes a seqnum mismatch that is transmitted to the op
auto bytes = create_frame(0, msg1);
@@ -188,19 +181,17 @@ BOOST_AUTO_TEST_CASE(read_parsing_error)
BOOST_AUTO_TEST_CASE(read_io_error)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return read(seqnum);
BOOST_ASIO_CORO_YIELD return st.read(seqnum);
BOOST_TEST(ec == error_code(client_errc::wrong_num_params));
}
return next_action();
@@ -212,7 +203,7 @@ BOOST_AUTO_TEST_CASE(read_io_error)
// Initial run yields a read request. We don't have cached data, so run_op returns it
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
// Read request fails with an error
act = algo.resume(client_errc::wrong_num_params, 0);
@@ -223,16 +214,14 @@ BOOST_AUTO_TEST_CASE(read_io_error)
BOOST_AUTO_TEST_CASE(read_ssl_active)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_TEST(ec == error_code());
return read(seqnum);
return st.read(seqnum);
}
};
@@ -242,7 +231,7 @@ BOOST_AUTO_TEST_CASE(read_ssl_active)
// Yielding a read with ssl active sets the use_ssl flag
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::read);
BOOST_TEST(act.type() == next_action_type::read);
BOOST_TEST(act.read_args().buffer.data() == st.reader.buffer().data());
BOOST_TEST(act.read_args().buffer.size() == st.reader.buffer().size());
BOOST_TEST(act.read_args().use_ssl);
@@ -250,19 +239,17 @@ BOOST_AUTO_TEST_CASE(read_ssl_active)
BOOST_AUTO_TEST_CASE(write_short)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return write(mock_message{msg1}, seqnum);
BOOST_ASIO_CORO_YIELD return st.write(mock_message{msg1}, seqnum);
BOOST_TEST(ec == error_code());
BOOST_TEST(seqnum == 1u);
}
@@ -275,13 +262,13 @@ BOOST_AUTO_TEST_CASE(write_short)
// Initial run yields a write request
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::write);
BOOST_TEST(act.type() == next_action_type::write);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(act.write_args().buffer, create_frame(0, msg1));
BOOST_TEST(!act.write_args().use_ssl);
// Acknowledge part of the write. This will ask for more bytes to be written
act = algo.resume(error_code(), 4);
BOOST_TEST(act.type() == next_action::type_t::write);
BOOST_TEST(act.type() == next_action_type::write);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(act.write_args().buffer, msg1);
// Complete
@@ -291,19 +278,17 @@ BOOST_AUTO_TEST_CASE(write_short)
BOOST_AUTO_TEST_CASE(write_io_error)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
BOOST_TEST(ec == error_code());
BOOST_ASIO_CORO_YIELD return write(mock_message{msg1}, seqnum);
BOOST_ASIO_CORO_YIELD return st.write(mock_message{msg1}, seqnum);
BOOST_TEST(ec == error_code(client_errc::wrong_num_params));
}
return next_action();
@@ -315,7 +300,7 @@ BOOST_AUTO_TEST_CASE(write_io_error)
// Initial run yields a write request. Fail it
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::write);
BOOST_TEST(act.type() == next_action_type::write);
act = algo.resume(client_errc::wrong_num_params, 0);
// Done
@@ -324,16 +309,14 @@ BOOST_AUTO_TEST_CASE(write_io_error)
BOOST_AUTO_TEST_CASE(write_ssl_active)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
std::uint8_t seqnum{};
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data& st, error_code ec)
{
BOOST_TEST(ec == error_code());
return write(mock_message{msg1}, seqnum);
return st.write(mock_message{msg1}, seqnum);
}
};
@@ -343,20 +326,18 @@ BOOST_AUTO_TEST_CASE(write_ssl_active)
// Yielding a write request when ssl_active() returns an action with the flag set
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::write);
BOOST_TEST(act.type() == next_action_type::write);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(act.write_args().buffer, create_frame(0, msg1));
BOOST_TEST(act.write_args().use_ssl);
}
BOOST_AUTO_TEST_CASE(ssl_handshake)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
boost::asio::coroutine coro;
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data&, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
@@ -373,7 +354,7 @@ BOOST_AUTO_TEST_CASE(ssl_handshake)
// Initial run yields a SSL handshake request. These are always returned
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::ssl_handshake);
BOOST_TEST(act.type() == next_action_type::ssl_handshake);
// Fail the op
act = algo.resume(client_errc::wrong_num_params, 0);
@@ -384,13 +365,11 @@ BOOST_AUTO_TEST_CASE(ssl_handshake)
BOOST_AUTO_TEST_CASE(ssl_shutdown)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
coroutine coro;
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data&, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
@@ -407,7 +386,7 @@ BOOST_AUTO_TEST_CASE(ssl_shutdown)
// Initial run yields a SSL handshake request. These are always returned
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::ssl_shutdown);
BOOST_TEST(act.type() == next_action_type::ssl_shutdown);
// Fail the op
act = algo.resume(client_errc::wrong_num_params, 0);
@@ -418,13 +397,11 @@ BOOST_AUTO_TEST_CASE(ssl_shutdown)
BOOST_AUTO_TEST_CASE(connect)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
boost::asio::coroutine coro;
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data&, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
@@ -441,7 +418,7 @@ BOOST_AUTO_TEST_CASE(connect)
// Initial run yields a connect request. These are always returned
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::connect);
BOOST_TEST(act.type() == next_action_type::connect);
// Fail the op
act = algo.resume(client_errc::wrong_num_params, 0);
@@ -452,13 +429,11 @@ BOOST_AUTO_TEST_CASE(connect)
BOOST_AUTO_TEST_CASE(close)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
boost::asio::coroutine coro;
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data&, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{
@@ -475,7 +450,7 @@ BOOST_AUTO_TEST_CASE(close)
// Initial run yields a close request. These are always returned
auto act = algo.resume(error_code(), 0);
BOOST_TEST(act.type() == next_action::type_t::close);
BOOST_TEST(act.type() == next_action_type::close);
// Fail the op
act = algo.resume(client_errc::wrong_num_params, 0);
@@ -486,13 +461,11 @@ BOOST_AUTO_TEST_CASE(close)
BOOST_AUTO_TEST_CASE(immediate_completion)
{
struct mock_algo : sansio_algorithm
struct mock_algo
{
boost::asio::coroutine coro;
mock_algo(connection_state_data& st) : sansio_algorithm(st) {}
next_action resume(error_code ec)
next_action resume(connection_state_data&, error_code ec)
{
BOOST_ASIO_CORO_REENTER(coro)
{

Some files were not shown because too many files have changed in this diff Show More