mirror of
https://github.com/boostorg/mysql.git
synced 2026-07-21 13:23:46 +00:00
committed by
GitHub
parent
22eddece25
commit
9ba4f9dfed
+1
-1
@@ -99,7 +99,7 @@ ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeIsMainRegex: "([-_](test|unittest))?$"
|
||||
IncludeIsMainRegex: null
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
|
||||
+1
-1
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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>
|
||||
|
||||
@@ -24,10 +24,13 @@
|
||||
'FieldViewFwdIterator',
|
||||
'Formattable',
|
||||
'OutputString',
|
||||
'PipelineRequestType',
|
||||
'PipelineStageType',
|
||||
'ResultsType',
|
||||
'SocketStream',
|
||||
'StaticRow',
|
||||
'Stream',
|
||||
'WritableField',
|
||||
'WritableFieldTuple'
|
||||
"/>
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ set(REGULAR_EXAMPLES
|
||||
metadata
|
||||
ssl
|
||||
timeouts
|
||||
pipeline
|
||||
)
|
||||
|
||||
foreach(FILE_NAME ${REGULAR_EXAMPLES})
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, ¶ms, 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
|
||||
|
||||
//]
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_(¶ms),
|
||||
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 : ¤t_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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 << " }";
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user