mirror of
https://github.com/boostorg/mysql.git
synced 2025-05-12 14:11:41 +00:00
parent
f76e17c37b
commit
a20fc3e5ea
@ -8,12 +8,11 @@
|
||||
[section:sql_formatting (Experimental) Client-side SQL query formatting]
|
||||
[nochunk]
|
||||
|
||||
When issuing queries that contain untrusted input, prepared statement are usually
|
||||
the way to go. However, some use cases like dynamic filters or batch inserts
|
||||
are not attainable using them.
|
||||
|
||||
The functions described in this section can be used to compose SQL query
|
||||
strings dynamically client-side while keeping your application secure.
|
||||
Client-side SQL formatting allows running SQL queries with user-supplied parameters securely.
|
||||
It can be used as a simpler and more flexible alternative to prepared statements.
|
||||
While prepared statements expand queries server-side, SQL formatting does it client-side.
|
||||
Please read the [link mysql.sql_formatting.comparison comparison with prepared statements]
|
||||
and the [link mysql.sql_formatting.security security considerations] sections for more info.
|
||||
|
||||
[note
|
||||
This feature is experimental. Its API may change in subsequent releases.
|
||||
@ -21,52 +20,23 @@ strings dynamically client-side while keeping your application secure.
|
||||
|
||||
|
||||
|
||||
[heading Common use cases]
|
||||
|
||||
* Queries involving several [*dynamic filters].
|
||||
See [link mysql.examples.dynamic_filters this example].
|
||||
* [*Batch inserts]. Inserting rows one by one can lead to poor efficiency.
|
||||
You can use client-side query formatting to compose a single `INSERT` that
|
||||
performs several insertions at once. See [link mysql.examples.batch_inserts this example]
|
||||
for simple batch inserts and [link mysql.examples.batch_inserts_generic this other] for
|
||||
a generic utility to implement this case.
|
||||
* [*PATCH-like updates]. If your query must update fields dynamically, based on some input,
|
||||
prepared statements may not be suitable. See [link mysql.examples.patch_updates this example].
|
||||
* Queries involving [*dynamic identifiers], like table and field names.
|
||||
* Performing [*conditional sorting].
|
||||
* Any other use case that involves SQL that can change dynamically.
|
||||
|
||||
This feature can also be used to improve efficiency, as text queries perform less
|
||||
round-trips to the server. See [link mysql.sql_formatting.efficiency this section]
|
||||
for more info.
|
||||
|
||||
|
||||
[heading Security considerations]
|
||||
|
||||
Client-side SQL formatting [*does protect against SQL injection] by appropriately quoting and escaping
|
||||
string values. However, this feature [*does not understand your queries]. You need to make sure that
|
||||
your formatting operations result in valid SQL. If you try to build very complex
|
||||
queries and make mistakes, attackers may exploit logic errors in your formatting logic to overtake your system.
|
||||
|
||||
|
||||
|
||||
[heading Formatting simple queries]
|
||||
[heading Simple queries]
|
||||
|
||||
You can use [reflink format_sql] to generate a SQL query from a format string
|
||||
and a set of parameters, using a notation similar to `std::format`:
|
||||
[reflink with_params] takes a SQL query
|
||||
string with placeholders and a set of parameters. When passed to
|
||||
[refmemunq any_connection execute] or [refmemunq any_connection async_execute],
|
||||
the query is expanded in the client with the supplied parameters and
|
||||
sent to the server for execution:
|
||||
|
||||
[sql_formatting_simple]
|
||||
|
||||
[reflink format_sql] doesn't involve communication with the server.
|
||||
In order to work, it requires a [reflink format_options] instance describing
|
||||
connection configuration, like the character set currently in use.
|
||||
[refmem any_connection format_opts] provides an easy way to retrieve these.
|
||||
Curly braces (`{}`) represent placeholders (technically called ['replacement fields]).
|
||||
The notation and semantics are similar to [@https://en.cppreference.com/w/cpp/utility/format/format `std::format`].
|
||||
|
||||
[note
|
||||
Getting an `unknown_character_set` error? Have a look at [link mysql.sql_formatting.unknown_character_set this section].
|
||||
]
|
||||
|
||||
All fundamental types can be used with query formatting. This includes integers, floating point types,
|
||||
All fundamental types can be used as query parameters. This includes integers, floating point types,
|
||||
strings, blobs, dates and times:
|
||||
|
||||
[sql_formatting_other_scalars]
|
||||
@ -84,7 +54,7 @@ and [link mysql.sql_formatting.reference this table] for a reference of types
|
||||
that have built-in support for SQL formatting.
|
||||
|
||||
[note
|
||||
Like with `std::format`, the format string passed to `format_sql` must be known at
|
||||
Like with `std::format`, the query string passed to `with_params` must be known at
|
||||
compile-time. You can skip this check using the [reflink runtime] function.
|
||||
]
|
||||
|
||||
@ -92,12 +62,106 @@ Like `std::format`, you can use arguments with explicit indices:
|
||||
|
||||
[sql_formatting_manual_indices]
|
||||
|
||||
You can also use named arguments, using the initializer list overload:
|
||||
See [link mysql.sql_formatting_advanced.format_string_syntax this section]
|
||||
for a reference on the format string syntax.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[heading:errors Common errors and how to fix them]
|
||||
|
||||
Not all values can be formatted. If the library finds that formatting a certain
|
||||
value can cause an ambiguity that could lead to a security problem, an error
|
||||
will be issued and the query won't be sent to the server. Here are the most common errors:
|
||||
|
||||
* `client_errc::invalid_encoding`
|
||||
* Cause: one of your string parameters contains invalid code points.
|
||||
With the default character set, this means that it contains [*invalid UTF-8].
|
||||
* Solution: all string values must be encoded according to the connection's character
|
||||
set (usually UTF-8). Sanitize or reject such values. Use the [reflink blob] and [reflink blob_view]
|
||||
types for values that don't represent character strings, but arbitrary binary values.
|
||||
* `client_errc::unformattable_value`
|
||||
* Cause: one of your parameters contains an invalid value. For instance, a `double`
|
||||
contains a `NaN` or an `Inf`, unsupported by MySQL.
|
||||
* Solution: reject such values, or replace them by `NULL` before passing them to client-side SQL formatting.
|
||||
* `client_errc::unknown_character_set`
|
||||
* Cause: your connection doesn't know the character set you're using. Knowing the character set in use
|
||||
is required to generate queries securely. This situation can happen after calling [refmemunq any_connection reset_connection]
|
||||
or if you used a custom [refmem connect_params connection_collation] when connecting.
|
||||
* Solution: use a [reflink connection_pool] instead of manually resetting connections. If you can't,
|
||||
use the default [refmemunq connect_params connection_collation] when connecting, and use
|
||||
[refmemunq any_connection set_character_set] or [refmemunq any_connection async_set_character_set]
|
||||
after resetting connections.
|
||||
* [link mysql.charsets.tracking Learn more] about how character set tracking works.
|
||||
|
||||
For example:
|
||||
|
||||
[sql_formatting_invalid_encoding]
|
||||
|
||||
|
||||
|
||||
|
||||
[heading Formatting queries without executing them]
|
||||
|
||||
`with_params` is handy, but may fall short in some cases involving queries with
|
||||
complex logic. For these cases, you can use [reflink format_sql] and
|
||||
[reflink format_sql_to] to expand a query without executing it.
|
||||
These APIs don't involve communication with the server.
|
||||
|
||||
[reflink format_sql] is the simplest, and is akin to `std::format`:
|
||||
|
||||
[sql_formatting_format_sql]
|
||||
|
||||
`format_sql` requires a [reflink format_options] instance describing
|
||||
connection configuration, like the character set currently in use.
|
||||
[refmem any_connection format_opts] provides an easy way to retrieve these.
|
||||
[link mysql.sql_formatting_advanced.format_options This section] contains more info about `format_opts`.
|
||||
|
||||
Some use cases, usually involving conditionals, may not be
|
||||
expressible in terms of a single format string. In such cases, you can
|
||||
use [reflink format_context] and [reflink format_sql_to] to
|
||||
build query strings incrementally:
|
||||
|
||||
[sql_formatting_incremental_fn]
|
||||
[sql_formatting_incremental_use]
|
||||
|
||||
[reflink sequence] uses this feature to make formatting ranges easier.
|
||||
|
||||
Any type that works with `with_params` also does with `format_sql`
|
||||
and `format_sql_to`. These types are said to satisfy the [reflink Formattable] concept.
|
||||
[link mysql.sql_formatting.reference This table] summarizes such types.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[heading:ranges Formatting ranges with sequence]
|
||||
|
||||
The [reflink sequence] function can be used when the default range formatting isn't sufficient.
|
||||
If the elements in your range are not formattable, you can pass a user-defined function to `sequence`
|
||||
describing how to format each element:
|
||||
|
||||
[sql_formatting_sequence_1]
|
||||
|
||||
By default, elements are separated by commas, but this is configurable:
|
||||
|
||||
[sql_formatting_sequence_2]
|
||||
|
||||
You can use `sequence` and [reflink with_params] together.
|
||||
|
||||
|
||||
|
||||
[sql_formatting_named_args]
|
||||
|
||||
See [link mysql.sql_formatting_advanced.format_string_syntax this section] for an in-depth
|
||||
explanation on format strings.
|
||||
|
||||
|
||||
|
||||
@ -120,84 +184,48 @@ This is equivalent to the previous snippet:
|
||||
|
||||
|
||||
|
||||
[heading Building SQL strings incrementally using format_sql_to]
|
||||
|
||||
Some use cases, usually involving conditionals, may not be
|
||||
expressible in terms of a single format string. In such cases, you can
|
||||
use [reflink format_context] and [reflink format_sql_to] to
|
||||
build query strings incrementally:
|
||||
|
||||
[sql_formatting_incremental_fn]
|
||||
[sql_formatting_incremental_use]
|
||||
|
||||
|
||||
|
||||
[heading:comparison Prepared statements vs. client-side SQL formatting]
|
||||
|
||||
Although both serve a similar purpose, they are fundamentally different. Prepared statements
|
||||
are parsed and expanded by the server. Client-side SQL expands the query in the client
|
||||
and sends it to the server as a string.
|
||||
|
||||
This means that [*client-side SQL does not understand your queries]. It just knows about how
|
||||
to format MySQL types into a string without creating vulnerabilities, but otherwise treats
|
||||
your queries as opaque strings. Client-side SQL yields [*greater flexibility] (you can dynamically
|
||||
compose any query), while statements have more limitations. This also means that
|
||||
[*you need to pay more attention to compose valid queries], specially when dealing with complex conditionals.
|
||||
Logic errors may lead to exploits. Please read the [link mysql.sql_formatting.security security considerations section]
|
||||
for more info.
|
||||
|
||||
[heading:ranges Formatting ranges with sequence]
|
||||
Client-side SQL entails [*less round-trips to the server] than statements, and is usually more efficient
|
||||
for lightweight queries. However, it uses the less compact text protocol, which may be slower for
|
||||
queries retrieving a lot of data. See the [link mysql.sql_formatting.efficiency efficiency considerations section] for more info.
|
||||
|
||||
The [reflink sequence] function can be used when the default range formatting isn't sufficient.
|
||||
If the elements in your range are not formattable, you can pass a user-defined function to `sequence`
|
||||
describing how to format each element:
|
||||
In general, [*use client-side SQL] formatting for the following cases:
|
||||
|
||||
[sql_formatting_sequence_1]
|
||||
|
||||
By default, elements are separated by commas, but this is configurable:
|
||||
|
||||
[sql_formatting_sequence_2]
|
||||
|
||||
|
||||
|
||||
|
||||
[heading:unknown_character_set Solving the unknown_character_set error]
|
||||
|
||||
If you are getting a `boost::system::system_error` with a
|
||||
`client_errc::unknown_character_set` error code (or getting this error code by other means),
|
||||
your connection is currently unaware of the character set it's using, which is required by format operations.
|
||||
Try the following:
|
||||
|
||||
* Make sure that you are performing connection establishment ([refmem any_connection connect] or
|
||||
[refmemunq any_connection async_connect]) before calling [refmemunq any_connection format_opts].
|
||||
* Use [refmem any_connection set_character_set] or [refmemunq any_connection async_set_character_set]
|
||||
to set your connection's character set instead of using raw SQL.
|
||||
* Some [refmem connect_params connection_collation] values are not supported by all servers
|
||||
and often trigger fallback behavior. If you are using a non-UTF8 character set, prefer setting it
|
||||
explicitly using [refmemunq any_connection set_character_set] or [refmemunq any_connection async_set_character_set].
|
||||
Don't rely on [refmem connect_params connection_collation].
|
||||
* [refmem any_connection reset_connection] and [refmemunq any_connection async_reset_connection] wipe
|
||||
character set information. Call [refmemunq any_connection set_character_set] or [refmemunq any_connection async_set_character_set]
|
||||
after resetting your connection.
|
||||
|
||||
[warning
|
||||
[*Security considerations]: don't craft [reflink format_options] values manually.
|
||||
Always use [refmem any_connection format_opts].
|
||||
]
|
||||
|
||||
For an explanation on why [reflink format_options] is necessary and how
|
||||
character set tracking works, please read [link mysql.sql_formatting_advanced.format_options this section].
|
||||
|
||||
|
||||
|
||||
|
||||
[heading Solving the invalid_encoding error]
|
||||
|
||||
SQL formatting can fail if you provide values that can't be securely formatted.
|
||||
The most common cause is passing string values that are not valid according to the
|
||||
passed character set. This triggers a `client_errc::invalid_encoding` error:
|
||||
|
||||
[sql_formatting_invalid_encoding]
|
||||
|
||||
You can validate your strings beforehand or handle the error once
|
||||
it happened and reject the input. Other types may also produce format errors.
|
||||
|
||||
[tip
|
||||
If you prefer handling errors with error codes, instead of exceptions,
|
||||
use [reflink format_sql_to]. Please read
|
||||
[link mysql.sql_formatting_advanced.error_handling this section] for details.
|
||||
]
|
||||
* Simple queries that don't retrieve a lot of data. Default to `with_params` and
|
||||
only switch to statements if your performance measurements says so.
|
||||
* Queries involving dynamic SQL that can't be achieved by statements. Typical cases include:
|
||||
* Dynamic filters ([link mysql.examples.dynamic_filters example]).
|
||||
* Batch inserts. Inserting rows one by one can lead to poor efficiency.
|
||||
You can use client-side SQL formatting to compose a single `INSERT` that
|
||||
inserts several rows at once (see [link mysql.examples.batch_inserts example 1]
|
||||
and [link mysql.examples.batch_inserts_generic example 2]).
|
||||
* PATCH-like updates, where the field list in an `UPDATE` must be dynamic
|
||||
([link mysql.examples.patch_updates example]).
|
||||
* Queries involving dynamic identifiers, like table and field names.
|
||||
* Conditional sorting.
|
||||
* Pipelines consisting of several semicolon-separated queries with dynamic fields.
|
||||
|
||||
On the other hand, [*prefer prepared statements] if:
|
||||
|
||||
* You are executing the same query over and over. You can prepare the statement
|
||||
once and execute it several times.
|
||||
* Your query is retrieving a lot of data, and you have performed the relevant performance measurements.
|
||||
|
||||
|
||||
|
||||
@ -219,9 +247,55 @@ Both client-side SQL formatting and prepared statements have pros and cons effic
|
||||
* [*Prepared statements can be re-used]. If you need to execute a query several times,
|
||||
prepared statements will only be parsed once.
|
||||
* Client-formatted SQL allows [*more efficient patterns] than prepared statements,
|
||||
like batch inserts.
|
||||
* You can use client-formatted SQL to generate several [*semicolon-separated queries]
|
||||
to be run in batch.
|
||||
like batch inserts and semicolon-separated queries.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[heading:security Security considerations]
|
||||
|
||||
Both client-side SQL formatting and prepared statements [*protect against SQL injection].
|
||||
Statements do so by parsing the query with placeholders server-side, before performing parameter
|
||||
substitution. Client-side SQL quotes and escapes your values to avoid injection, but
|
||||
[*does not understand your queries].
|
||||
|
||||
This means that you need to [*ensure that your queries always expand to valid SQL].
|
||||
This is trivial for simple queries, but may be an issue with more complex ones,
|
||||
involving ranges or dynamic identifiers. For instance, the following query may
|
||||
expand to invalid SQL if the provided range is empty:
|
||||
|
||||
[sql_formatting_empty_ranges]
|
||||
|
||||
The risk is higher if you're building your query by pieces using [reflink format_sql_to].
|
||||
|
||||
To sum up:
|
||||
|
||||
* Client-side SQL protects against SQL injection.
|
||||
* Client-side SQL does not protect against logic errors. The risk is only present in complex
|
||||
queries. We suggest the following advice:
|
||||
* Avoid complex query generation logic as much as possible.
|
||||
Use a single format string instead of `format_sql_to`, unless you have no other option.
|
||||
* When using ranges, consider if the empty range would lead to valid SQL or not.
|
||||
* Thoroughly test complex query generation logic.
|
||||
* Client-side SQL requires knowing the connection's current character set. This usually happens
|
||||
out of the box, and will lead to a [link mysql.sql_formatting.errors controlled error]
|
||||
otherwise. Some recommendations:
|
||||
* If in doubt, always use the default character set (`utf8mb4`).
|
||||
* Never issue `SET NAMES` or `SET CHARACTER SET` statements directly -
|
||||
use [refmem any_connection set_character_set] or [refmemunq any_connection async_set_character_set], instead.
|
||||
* If you're using [reflink format_sql] or [reflink format_sql_to], never craft [reflink format_options] values manually.
|
||||
Use [refmem any_connection format_opts], instead.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ You can specialize [reflink formatter] to add formatting support to your types:
|
||||
|
||||
[sql_formatting_formatter_specialization]
|
||||
|
||||
The type can now be used in [reflink format_sql] and [reflink format_sql_to]:
|
||||
The type can now be used in [reflink format_sql], [reflink format_sql_to] and [reflink with_params]:
|
||||
|
||||
[sql_formatting_formatter_use]
|
||||
|
||||
@ -32,6 +32,10 @@ We can now use it like this:
|
||||
See the [reflink formatter] reference docs for more info.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[heading:format_string_syntax Format string syntax]
|
||||
|
||||
This section extends on the supported syntax for format strings.
|
||||
@ -52,20 +56,10 @@ Indices can appear in any order, and can be repeated:
|
||||
|
||||
[sql_formatting_manual_indices]
|
||||
|
||||
Finally, you can use named arguments by using the initializer-list overloads,
|
||||
which creates [reflink format_arg] values:
|
||||
|
||||
[sql_formatting_named_args]
|
||||
|
||||
Argument names can only contain ASCII letters (lowercase and uppercase),
|
||||
digits and the underscore character (`_`). Names can't start with a digit.
|
||||
|
||||
Format strings can use either manual or automatic indexing, but can't mix them:
|
||||
|
||||
[sql_formatting_manual_auto_mix]
|
||||
|
||||
Named arguments can be mixed with either manual or automatic indexing.
|
||||
|
||||
Unreferenced format arguments are ignored. It's not an error to supply more
|
||||
format arguments than required:
|
||||
|
||||
@ -79,8 +73,8 @@ Format specifiers (e.g. `{:i}`) are supported for some types,
|
||||
but are far less common than in fmtlib, since most types have a
|
||||
single, canonical representation.
|
||||
|
||||
Specifiers can appear when doing automatic indexing (e.g. `{:i}`),
|
||||
manual indexing (e.g. `{0:i}`) and named arguments (e.g. `{name:i}`).
|
||||
Specifiers can appear when doing automatic indexing (e.g. `{:i}`) or
|
||||
manual indexing (e.g. `{0:i}`).
|
||||
|
||||
Types specializing formatters can define custom specifiers.
|
||||
Only printable ASCII characters that are not `{` or `}` can be used as specifiers.
|
||||
@ -99,6 +93,10 @@ Some values can't be securely formatted. For instance, C++
|
||||
Strings can contain byte sequences that don't represent valid characters,
|
||||
which makes them impossible to escape securely.
|
||||
|
||||
When using [reflink with_params] and any of these errors is encountered,
|
||||
the [refmemunq any_connection execute] operation fails, as if a server error
|
||||
had been encountered. This is transparent to the user, so no action is required.
|
||||
|
||||
[reflink format_sql] reports these errors by throwing `boost::system::system_error` exceptions,
|
||||
which contain an error code with details about what happened. For instance:
|
||||
|
||||
@ -144,18 +142,24 @@ that formatting functions need to know in order to work:
|
||||
[link mysql.error_handling.system_result `boost::system::result`]`<`[reflink format_options]`>`.
|
||||
If the connection could not determine the current character set, the result will contain an error.
|
||||
For a reference on how character set tracking works, please read [link mysql.charsets.tracking this section].
|
||||
|
||||
[note
|
||||
Prior to connection establishment, the connection's character set is always unknown.
|
||||
Connect your connection before calling `format_opts`.
|
||||
]
|
||||
|
||||
[warning
|
||||
Passing an incorrect `format_options` value to formatting functions may cause
|
||||
escaping to generate incorrect values, which may generate vulnerabilities.
|
||||
Stay safe and always use [refmem any_connection format_opts] instead of
|
||||
hand-crafting `format_options` values. Doing this, if the character set can't be safely
|
||||
determined, you will get a [link mysql.sql_formatting.unknown_character_set `client_errc::unknown_character_set`]
|
||||
error instead of a vulnerability.
|
||||
determined, you will get a `client_errc::unknown_character_set` error instead of a vulnerability.
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[heading Custom string types]
|
||||
|
||||
[reflink format_sql_to] can be used with string types that are not `std::string`,
|
||||
|
@ -8,8 +8,9 @@
|
||||
[section:boost__mysql__ExecutionRequest ExecutionRequest concept]
|
||||
|
||||
An execution request represents a SQL statement to be executed by the
|
||||
server, plus any parameters required to run the query. It may model
|
||||
a plain text query, or a prepared statement handle with bound parameters.
|
||||
server, plus any required parameters. It may model
|
||||
a plain text query, a client-side formatted query with parameters,
|
||||
or a prepared statement handle with parameters.
|
||||
|
||||
Formally, a type `T` is a `ExecutionRequest` if it fulfills any of the following:
|
||||
|
||||
@ -19,6 +20,8 @@ Formally, a type `T` is a `ExecutionRequest` if it fulfills any of the following
|
||||
reference to it.
|
||||
* An instantiation of the [reflink bound_statement_iterator_range] class, or a (possibly cv-qualified)
|
||||
reference to it.
|
||||
* An instantiation of the [reflink with_params_t] class, or a (possibly cv-qualified)
|
||||
reference to it.
|
||||
|
||||
This definition may be extended in future versions, but the above types will still satisfy `ExecutionRequest`.
|
||||
|
||||
|
@ -62,6 +62,7 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__static_execution_state">static_execution_state</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>
|
||||
<member><link linkend="mysql.ref.boost__mysql__with_params_t">with_params_t</link></member>
|
||||
</simplelist>
|
||||
</entry>
|
||||
<entry valign="top">
|
||||
@ -103,6 +104,7 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__runtime">runtime</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__sequence">sequence</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__throw_on_error">throw_on_error</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__with_params">with_params</link></member>
|
||||
</simplelist>
|
||||
</entry>
|
||||
<entry valign="top">
|
||||
@ -113,6 +115,7 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__days">days</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__error_code">error_code</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__format_context">format_context</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__make_tuple_element_t">make_tuple_element_t</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__metadata_collection_view">metadata_collection_view</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__string_view">string_view</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__tcp_connection">tcp_connection</link></member>
|
||||
|
@ -22,16 +22,11 @@
|
||||
// Note: client-side SQL formatting is an experimental feature.
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/character_set.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/format_sql.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/describe/class.hpp>
|
||||
#include <boost/describe/members.hpp>
|
||||
#include <boost/describe/modifiers.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
@ -41,9 +36,6 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using boost::mysql::error_code;
|
||||
using boost::mysql::string_view;
|
||||
|
||||
/**
|
||||
* We will use Boost.Describe to easily parse the JSON file
|
||||
* into a std::vector<employee>. The JSON file contain an array
|
||||
@ -75,50 +67,6 @@ static std::string read_file(const char* file_name)
|
||||
return std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
// Composes an INSERT SQL query suitable to be sent to the server.
|
||||
// For instance, when inserting two employees, something like the following may be generated:
|
||||
// INSERT INTO employee (first_name, last_name, company_id, salary)
|
||||
// VALUES ('John', 'Doe', 'HGS', 20000), ('Rick', 'Smith', 'LLC', 50000)
|
||||
static std::string compose_batch_insert(
|
||||
// Connection config options required for the formatting.
|
||||
// This includes the character set currently in use.
|
||||
boost::mysql::format_options opts,
|
||||
|
||||
// The list of employees to insert, as read from the JSON file
|
||||
const std::vector<employee>& employees
|
||||
)
|
||||
{
|
||||
// We need at least one employee to insert
|
||||
assert(!employees.empty());
|
||||
|
||||
// A function describing how to format a single employee object. Used with mysql::sequence.
|
||||
auto format_employee_fn = [](const employee& emp, boost::mysql::format_context_base& ctx) {
|
||||
// format_context_base can be used to build query strings incrementally.
|
||||
// Used internally by the sequence() formatter
|
||||
// format_sql_to expands a format string, replacing {} fields,
|
||||
// and appends the result to the passed context.
|
||||
// When formatted, strings are quoted and escaped as string literals.
|
||||
// Doubles are formatted as number literals.
|
||||
boost::mysql::format_sql_to(
|
||||
ctx,
|
||||
"({}, {}, {}, {})",
|
||||
emp.first_name,
|
||||
emp.last_name,
|
||||
emp.company_id,
|
||||
emp.salary
|
||||
);
|
||||
};
|
||||
|
||||
// sequence() takes a range and a formatter function.
|
||||
// It will call the formatter function for each object in the sequence,
|
||||
// adding commas between invocations.
|
||||
return boost::mysql::format_sql(
|
||||
opts,
|
||||
"INSERT INTO employee (first_name, last_name, company_id, salary) VALUES {}",
|
||||
boost::mysql::sequence(employees, format_employee_fn)
|
||||
);
|
||||
}
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5)
|
||||
@ -156,19 +104,42 @@ void main_impl(int argc, char** argv)
|
||||
params.password = argv[2];
|
||||
params.database = "boost_mysql_examples";
|
||||
|
||||
// A results object to hold the result of executing our SQL query
|
||||
boost::mysql::results result;
|
||||
|
||||
// Connect to the server
|
||||
conn.connect(params);
|
||||
|
||||
// Compose the query. format_opts() returns a system::result<format_options>,
|
||||
// containing the options required by format_context. format_opts() may return
|
||||
// an error if the connection doesn't know which character set is using -
|
||||
// use set_character_set if this happens.
|
||||
std::string query = compose_batch_insert(conn.format_opts().value(), values);
|
||||
// A function describing how to format a single employee object. Used with mysql::sequence.
|
||||
auto format_employee_fn = [](const employee& emp, boost::mysql::format_context_base& ctx) {
|
||||
// format_context_base can be used to build query strings incrementally.
|
||||
// Used internally by the sequence() formatter.
|
||||
// format_sql_to expands a format string, replacing {} fields,
|
||||
// and appends the result to the passed context.
|
||||
// When formatted, strings are quoted and escaped as string literals.
|
||||
// ints are formatted as number literals.
|
||||
boost::mysql::format_sql_to(
|
||||
ctx,
|
||||
"({}, {}, {}, {})",
|
||||
emp.first_name,
|
||||
emp.last_name,
|
||||
emp.company_id,
|
||||
emp.salary
|
||||
);
|
||||
};
|
||||
|
||||
// Execute the query as usual. Note that, unlike with prepared statements,
|
||||
// formatting happened in the client, and not in the server.
|
||||
boost::mysql::results result;
|
||||
conn.execute(query, result);
|
||||
// Compose and execute the batch INSERT. When passed to execute(), with_params
|
||||
// replaces placeholders ({}) by actual parameter values before sending the query to the server.
|
||||
// When inserting two employees, something like the following may be generated:
|
||||
// INSERT INTO employee (first_name, last_name, company_id, salary)
|
||||
// VALUES ('John', 'Doe', 'HGS', 20000), ('Rick', 'Smith', 'LLC', 50000)
|
||||
conn.execute(
|
||||
boost::mysql::with_params(
|
||||
"INSERT INTO employee (first_name, last_name, company_id, salary) VALUES {}",
|
||||
boost::mysql::sequence(values, format_employee_fn)
|
||||
),
|
||||
result
|
||||
);
|
||||
std::cout << "Done\n";
|
||||
|
||||
// Notify the MySQL server we want to quit, then close the underlying connection.
|
||||
|
@ -5,16 +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/constant_string_view.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <boost/describe/class.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/mp11/tuple.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef BOOST_DESCRIBE_CXX14
|
||||
|
||||
@ -31,25 +22,27 @@
|
||||
// Note: client-side SQL formatting is an experimental feature.
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/format_sql.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/describe/class.hpp>
|
||||
#include <boost/describe/members.hpp>
|
||||
#include <boost/describe/modifiers.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/mp11/tuple.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using boost::mysql::error_code;
|
||||
using boost::mysql::string_view;
|
||||
namespace describe = boost::describe;
|
||||
namespace mp11 = boost::mp11;
|
||||
@ -165,21 +158,17 @@ void main_impl(int argc, char** argv)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Compose the query. We use sequence() to format C++ ranges as
|
||||
// comma-separated sequences.
|
||||
// Recall that format_opts() returns a system::result<format_options>,
|
||||
// which can contain an error if the connection doesn't know which character set is using.
|
||||
// Use set_character_set if this happens.
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"INSERT INTO employee ({::i}) VALUES {}",
|
||||
get_field_names<employee>(),
|
||||
boost::mysql::sequence(values, insert_struct_format_fn())
|
||||
);
|
||||
|
||||
// Execute the query as usual.
|
||||
// Run the query. Placeholders ({}) will be expanded before the query is sent to the server.
|
||||
// We use sequence() to format C++ ranges as comma-separated sequences.
|
||||
boost::mysql::results result;
|
||||
conn.execute(query, result);
|
||||
conn.execute(
|
||||
boost::mysql::with_params(
|
||||
"INSERT INTO employee ({::i}) VALUES {}",
|
||||
get_field_names<employee>(),
|
||||
boost::mysql::sequence(values, insert_struct_format_fn())
|
||||
),
|
||||
result
|
||||
);
|
||||
std::cout << "Done\n";
|
||||
|
||||
// Notify the MySQL server we want to quit, then close the underlying connection.
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <boost/mysql/static_results.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/throw_on_error.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
@ -71,6 +72,9 @@ std::vector<note_t> note_repository::get_notes(boost::asio::yield_context yield)
|
||||
std::make_move_iterator(result.rows().begin()),
|
||||
std::make_move_iterator(result.rows().end())
|
||||
);
|
||||
|
||||
// If an exception is thrown, pooled_connection's destructor will
|
||||
// return the connection automatically to the pool.
|
||||
}
|
||||
|
||||
optional<note_t> note_repository::get_note(std::int64_t note_id, boost::asio::yield_context yield)
|
||||
@ -84,31 +88,25 @@ optional<note_t> note_repository::get_note(std::int64_t note_id, boost::asio::yi
|
||||
mysql::pooled_connection conn = pool_.async_get_connection(diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Our query has a parameter, so we need a prepared statement.
|
||||
// We don't need to deallocate this statement explicitly
|
||||
// (no need to call any_connection::async_close_statement).
|
||||
// The connection pool takes care of this for us
|
||||
// (by using any_connection::async_reset_connection).
|
||||
mysql::statement stmt = conn->async_prepare_statement(
|
||||
"SELECT id, title, content FROM notes WHERE id = ?",
|
||||
// When executed, with_params expands a query client-side before sending it to the server.
|
||||
// Placeholders are marked with {}
|
||||
mysql::static_results<note_t> result;
|
||||
conn->async_execute(
|
||||
mysql::with_params("SELECT id, title, content FROM notes WHERE id = {}", note_id),
|
||||
result,
|
||||
diag,
|
||||
yield[ec]
|
||||
);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Execute the statement. We use the static interface to parse results
|
||||
mysql::static_results<note_t> result;
|
||||
conn->async_execute(stmt.bind(note_id), result, diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
// We did nothing to mutate session state, so we can skip reset
|
||||
conn.return_without_reset();
|
||||
|
||||
// An empty results object indicates that no note was found
|
||||
if (result.rows().empty())
|
||||
return {};
|
||||
else
|
||||
return std::move(result.rows()[0]);
|
||||
|
||||
// There's no need to return the connection explicitly to the pool,
|
||||
// pooled_connection's destructor takes care of it.
|
||||
}
|
||||
|
||||
note_t note_repository::create_note(string_view title, string_view content, boost::asio::yield_context yield)
|
||||
@ -122,9 +120,10 @@ note_t note_repository::create_note(string_view title, string_view content, boos
|
||||
mysql::pooled_connection conn = pool_.async_get_connection(diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Our query has parameters, so we need to prepare a statement.
|
||||
// As explained above, there is no need to deallocate the statement explicitly,
|
||||
// since the pool takes care of it.
|
||||
// We will use statements in this function for the sake of example.
|
||||
// We don't need to deallocate the statement explicitly,
|
||||
// since the pool takes care of it after the connection is returned.
|
||||
// You can also use with_params instead of statements.
|
||||
mysql::statement stmt = conn->async_prepare_statement(
|
||||
"INSERT INTO notes (title, content) VALUES (?, ?)",
|
||||
diag,
|
||||
@ -164,30 +163,30 @@ optional<note_t> note_repository::replace_note(
|
||||
mysql::pooled_connection conn = pool_.async_get_connection(diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Our query has parameters, so we need to prepare a statement.
|
||||
// As explained above, there is no need to deallocate the statement explicitly,
|
||||
// since the pool takes care of it.
|
||||
mysql::statement stmt = conn->async_prepare_statement(
|
||||
"UPDATE notes SET title = ?, content = ? WHERE id = ?",
|
||||
// Expand and execute the query.
|
||||
// It won't produce any rows, so we can use static_results<std::tuple<>>
|
||||
mysql::static_results<std::tuple<>> empty_result;
|
||||
conn->async_execute(
|
||||
mysql::with_params(
|
||||
"UPDATE notes SET title = {}, content = {} WHERE id = {}",
|
||||
title,
|
||||
content,
|
||||
note_id
|
||||
),
|
||||
empty_result,
|
||||
diag,
|
||||
yield[ec]
|
||||
);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Execute the statement. The statement won't produce any rows,
|
||||
// so we can use static_results<std::tuple<>>
|
||||
mysql::static_results<std::tuple<>> empty_result;
|
||||
conn->async_execute(stmt.bind(title, content, note_id), empty_result, diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
// We didn't mutate session state, so we can skip reset
|
||||
conn.return_without_reset();
|
||||
|
||||
// No affected rows means that the note doesn't exist
|
||||
if (empty_result.affected_rows() == 0u)
|
||||
return {};
|
||||
|
||||
return note_t{note_id, title, content};
|
||||
|
||||
// There's no need to return the connection explicitly to the pool,
|
||||
// pooled_connection's destructor takes care of it.
|
||||
}
|
||||
|
||||
bool note_repository::delete_note(std::int64_t note_id, boost::asio::yield_context yield)
|
||||
@ -201,23 +200,22 @@ bool note_repository::delete_note(std::int64_t note_id, boost::asio::yield_conte
|
||||
mysql::pooled_connection conn = pool_.async_get_connection(diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Our query has parameters, so we need to prepare a statement.
|
||||
// As explained above, there is no need to deallocate the statement explicitly,
|
||||
// since the pool takes care of it.
|
||||
mysql::statement stmt = conn->async_prepare_statement("DELETE FROM notes WHERE id = ?", diag, yield[ec]);
|
||||
// Expand and execute the query.
|
||||
// It won't produce any rows, so we can use static_results<std::tuple<>>
|
||||
mysql::static_results<std::tuple<>> empty_result;
|
||||
conn->async_execute(
|
||||
mysql::with_params("DELETE FROM notes WHERE id = {}", note_id),
|
||||
empty_result,
|
||||
diag,
|
||||
yield[ec]
|
||||
);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Execute the statement. The statement won't produce any rows,
|
||||
// so we can use static_results<std::tuple<>>
|
||||
mysql::static_results<std::tuple<>> empty_result;
|
||||
conn->async_execute(stmt.bind(note_id), empty_result, diag, yield[ec]);
|
||||
mysql::throw_on_error(ec, diag);
|
||||
// We didn't mutate session state, so we can skip reset
|
||||
conn.return_without_reset();
|
||||
|
||||
// No affected rows means that the note didn't exist
|
||||
return empty_result.affected_rows() != 0u;
|
||||
|
||||
// There's no need to return the connection explicitly to the pool,
|
||||
// pooled_connection's destructor takes care of it.
|
||||
}
|
||||
|
||||
//]
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/character_set.hpp>
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
@ -29,7 +28,6 @@
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
#include <cassert>
|
||||
@ -188,6 +186,12 @@ static cmdline_args parse_cmdline_args(int argc, char** argv)
|
||||
}
|
||||
|
||||
// Composes a SELECT query to retrieve employees according to the passed filters.
|
||||
// We allow an optional ORDER BY clause that must be added dynamically,
|
||||
// so we can't express our query as a single format string.
|
||||
// This function uses format_sql_to to build a query string incrementally.
|
||||
// format_sql_to requires us to pass a format_options value, containing configuration
|
||||
// options like the current character set. Use any_connection::format_opts to obtain it.
|
||||
// If your use case allows you to express your query as a single format string, use with_params, instead.
|
||||
std::string compose_get_employees_query(
|
||||
boost::mysql::format_options opts,
|
||||
const std::vector<filter>& filts,
|
||||
@ -228,7 +232,10 @@ std::string compose_get_employees_query(
|
||||
boost::mysql::format_sql_to(ctx, " ORDER BY {:i}", *order_by);
|
||||
}
|
||||
|
||||
// Get our generated query
|
||||
// Get our generated query. get() returns a system::result<std::string>, which
|
||||
// will contain errors if any of the args couldn't be formatted. This can happen
|
||||
// if you pass string values containing invalid UTF-8.
|
||||
// value() will throw an exception if that's the case.
|
||||
return std::move(ctx).get().value();
|
||||
}
|
||||
|
||||
@ -282,8 +289,9 @@ void main_impl(int argc, char** argv)
|
||||
|
||||
// Execute the query as usual. Note that, unlike with prepared statements,
|
||||
// formatting happened in the client, and not in the server.
|
||||
// Casting to string_view saves a copy in async_execute
|
||||
boost::mysql::results result;
|
||||
conn.async_execute(query, result, diag, yield[ec]);
|
||||
conn.async_execute(string_view(query), result, diag, yield[ec]);
|
||||
boost::mysql::throw_on_error(ec, diag);
|
||||
|
||||
// Print the employees
|
||||
|
@ -16,23 +16,18 @@
|
||||
// Note: client-side SQL formatting is an experimental feature.
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/format_sql.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/ssl_mode.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using boost::mysql::error_code;
|
||||
using boost::mysql::field_view;
|
||||
using boost::mysql::string_view;
|
||||
|
||||
@ -170,30 +165,32 @@ void main_impl(int argc, char** argv)
|
||||
// Formats an individual update. Used by sequence().
|
||||
// For update_field{"first_name", "John"}, it generates the string
|
||||
// "`first_name` = 'John'"
|
||||
// Format contexts can build a query string incrementally, and are used by sequence() internally
|
||||
auto update_format_fn = [](update_field upd, boost::mysql::format_context_base& ctx) {
|
||||
boost::mysql::format_sql_to(ctx, "{:i} = {}", upd.field_name, upd.field_value);
|
||||
};
|
||||
|
||||
// Compose the query. We use sequence() to output the update list separated by commas.
|
||||
// Compose and execute the query. with_params will expand placeholders
|
||||
// before sending the query to the server.
|
||||
// We use sequence() to output the update list separated by commas.
|
||||
// We want to update the employee and then retrieve it. MySQL doesn't support
|
||||
// the UPDATE ... RETURNING statement to update and retrieve data atomically,
|
||||
// so we will use a transaction to guarantee consistency.
|
||||
// Instead of running every statement separately, we activated params.multi_queries,
|
||||
// which allows semicolon-separated statements.
|
||||
// As in std::format, we can use explicit indices like {0} and {1} to reference arguments.
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"START TRANSACTION; "
|
||||
"UPDATE employee SET {0} WHERE id = {1}; "
|
||||
"SELECT first_name, last_name, salary, company_id FROM employee WHERE id = {1}; "
|
||||
"COMMIT",
|
||||
boost::mysql::sequence(args.updates, update_format_fn),
|
||||
args.employee_id
|
||||
);
|
||||
|
||||
// Execute the query as usual.
|
||||
boost::mysql::results result;
|
||||
conn.execute(query, result);
|
||||
conn.execute(
|
||||
boost::mysql::with_params(
|
||||
"START TRANSACTION; "
|
||||
"UPDATE employee SET {0} WHERE id = {1}; "
|
||||
"SELECT first_name, last_name, salary, company_id FROM employee WHERE id = {1}; "
|
||||
"COMMIT",
|
||||
boost::mysql::sequence(args.updates, update_format_fn),
|
||||
args.employee_id
|
||||
),
|
||||
result
|
||||
);
|
||||
|
||||
// We ran 4 queries, so the results object will hold 4 resultsets.
|
||||
// Get the rows retrieved by the SELECT (the 3rd one).
|
||||
|
@ -66,5 +66,6 @@
|
||||
#include <boost/mysql/underlying_row.hpp>
|
||||
#include <boost/mysql/unix.hpp>
|
||||
#include <boost/mysql/unix_ssl.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#endif
|
||||
|
@ -144,12 +144,8 @@ class any_connection
|
||||
static std::unique_ptr<detail::engine> create_engine(asio::any_io_executor ex, asio::ssl::context* ctx);
|
||||
|
||||
// Used by tests
|
||||
any_connection(
|
||||
std::size_t initial_buffer_size,
|
||||
std::size_t max_buffer_size,
|
||||
std::unique_ptr<detail::engine> eng
|
||||
)
|
||||
: impl_(initial_buffer_size, max_buffer_size, std::move(eng))
|
||||
any_connection(std::unique_ptr<detail::engine> eng, any_connection_params params)
|
||||
: impl_(params.initial_buffer_size, params.max_buffer_size, std::move(eng))
|
||||
{
|
||||
}
|
||||
|
||||
@ -164,11 +160,7 @@ public:
|
||||
* an \ref any_connection_params object to this constructor.
|
||||
*/
|
||||
any_connection(boost::asio::any_io_executor ex, any_connection_params params = {})
|
||||
: any_connection(
|
||||
params.initial_buffer_size,
|
||||
params.max_buffer_size,
|
||||
create_engine(std::move(ex), params.ssl_context)
|
||||
)
|
||||
: any_connection(create_engine(std::move(ex), params.ssl_context), params)
|
||||
{
|
||||
}
|
||||
|
||||
@ -408,18 +400,18 @@ public:
|
||||
|
||||
/// \copydoc connection::execute
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
{
|
||||
impl_.execute(req, result, err, diag);
|
||||
impl_.execute(std::forward<ExecutionRequest>(req), result, err, diag);
|
||||
}
|
||||
|
||||
/// \copydoc execute
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result)
|
||||
void execute(ExecutionRequest&& req, ResultsType& result)
|
||||
{
|
||||
error_code err;
|
||||
diagnostics diag;
|
||||
execute(req, result, err, diag);
|
||||
execute(std::forward<ExecutionRequest>(req), result, err, diag);
|
||||
detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
@ -463,25 +455,20 @@ public:
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(
|
||||
const ExecutionRequest& req,
|
||||
ExecutionStateType& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
void start_execution(ExecutionRequest&& req, ExecutionStateType& st, error_code& err, diagnostics& diag)
|
||||
{
|
||||
impl_.start_execution(req, st, err, diag);
|
||||
impl_.start_execution(std::forward<ExecutionRequest>(req), st, err, diag);
|
||||
}
|
||||
|
||||
/// \copydoc start_execution
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(const ExecutionRequest& req, ExecutionStateType& st)
|
||||
void start_execution(ExecutionRequest&& req, ExecutionStateType& st)
|
||||
{
|
||||
error_code err;
|
||||
diagnostics diag;
|
||||
start_execution(req, st, err, diag);
|
||||
start_execution(std::forward<ExecutionRequest>(req), st, err, diag);
|
||||
detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
|
@ -362,18 +362,18 @@ public:
|
||||
* Metadata in `result` will be populated according to `this->meta_mode()`.
|
||||
*/
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
{
|
||||
impl_.execute(req, result, err, diag);
|
||||
impl_.execute(std::forward<ExecutionRequest>(req), result, err, diag);
|
||||
}
|
||||
|
||||
/// \copydoc execute
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result)
|
||||
void execute(ExecutionRequest&& req, ResultsType& result)
|
||||
{
|
||||
error_code err;
|
||||
diagnostics diag;
|
||||
execute(req, result, err, diag);
|
||||
execute(std::forward<ExecutionRequest>(req), result, err, diag);
|
||||
detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
@ -461,25 +461,20 @@ public:
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(
|
||||
const ExecutionRequest& req,
|
||||
ExecutionStateType& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
void start_execution(ExecutionRequest&& req, ExecutionStateType& st, error_code& err, diagnostics& diag)
|
||||
{
|
||||
impl_.start_execution(req, st, err, diag);
|
||||
impl_.start_execution(std::forward<ExecutionRequest>(req), st, err, diag);
|
||||
}
|
||||
|
||||
/// \copydoc start_execution
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(const ExecutionRequest& req, ExecutionStateType& st)
|
||||
void start_execution(ExecutionRequest&& req, ExecutionStateType& st)
|
||||
{
|
||||
error_code err;
|
||||
diagnostics diag;
|
||||
start_execution(req, st, err, diag);
|
||||
start_execution(std::forward<ExecutionRequest>(req), st, err, diag);
|
||||
detail::throw_on_error_loc(err, diag, BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
|
@ -8,37 +8,75 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_ANY_EXECUTION_REQUEST_HPP
|
||||
#define BOOST_MYSQL_DETAIL_ANY_EXECUTION_REQUEST_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
class field_view;
|
||||
class format_arg;
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct any_execution_request
|
||||
{
|
||||
enum class type_t
|
||||
{
|
||||
query,
|
||||
query_with_params,
|
||||
stmt
|
||||
};
|
||||
|
||||
union data_t
|
||||
{
|
||||
string_view query;
|
||||
struct
|
||||
struct query_with_params_t
|
||||
{
|
||||
statement stmt;
|
||||
constant_string_view query;
|
||||
span<const format_arg> args;
|
||||
} query_with_params;
|
||||
struct stmt_t
|
||||
{
|
||||
std::uint32_t stmt_id;
|
||||
std::uint16_t num_params;
|
||||
span<const field_view> params;
|
||||
} stmt;
|
||||
|
||||
data_t(string_view q) noexcept : query(q) {}
|
||||
data_t(statement s, span<const field_view> params) noexcept : stmt{s, params} {}
|
||||
} data;
|
||||
bool is_query;
|
||||
data_t(query_with_params_t v) noexcept : query_with_params(v) {}
|
||||
data_t(stmt_t v) noexcept : stmt(v) {}
|
||||
};
|
||||
|
||||
any_execution_request(string_view q) noexcept : data(q), is_query(true) {}
|
||||
any_execution_request(statement s, span<const field_view> params) noexcept
|
||||
: data(s, params), is_query(false)
|
||||
type_t type;
|
||||
data_t data;
|
||||
|
||||
any_execution_request(string_view q) noexcept : type(type_t::query), data(q) {}
|
||||
any_execution_request(data_t::query_with_params_t v) noexcept : type(type_t::query_with_params), data(v)
|
||||
{
|
||||
}
|
||||
any_execution_request(data_t::stmt_t v) noexcept : type(type_t::stmt), data(v) {}
|
||||
};
|
||||
|
||||
struct no_execution_request_traits
|
||||
{
|
||||
};
|
||||
|
||||
template <class T, class = void>
|
||||
struct execution_request_traits : no_execution_request_traits
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct execution_request_traits<T, typename std::enable_if<std::is_convertible<T, string_view>::value>::type>
|
||||
{
|
||||
static any_execution_request make_request(string_view input, std::vector<field_view>&) { return input; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
@ -22,23 +22,19 @@
|
||||
|
||||
#include <boost/mysql/detail/access.hpp>
|
||||
#include <boost/mysql/detail/algo_params.hpp>
|
||||
#include <boost/mysql/detail/any_execution_request.hpp>
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
#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/writable_field_traits.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
@ -52,12 +48,11 @@ class pipeline_request;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Forward decl
|
||||
class connection_state;
|
||||
|
||||
//
|
||||
// Helpers to interact with connection_state, without including its definition
|
||||
//
|
||||
class connection_state;
|
||||
|
||||
struct connection_state_deleter
|
||||
{
|
||||
BOOST_MYSQL_DECL void operator()(connection_state*) const;
|
||||
@ -72,71 +67,9 @@ any_resumable_ref setup(connection_state&, diagnostics&, const AlgoParams&);
|
||||
template <class AlgoParams>
|
||||
typename AlgoParams::result_type get_result(const connection_state&);
|
||||
|
||||
//
|
||||
// execution helpers
|
||||
//
|
||||
template <class... T, std::size_t... I>
|
||||
std::array<field_view, sizeof...(T)> tuple_to_array_impl(const std::tuple<T...>& t, mp11::index_sequence<I...>) noexcept
|
||||
{
|
||||
boost::ignore_unused(t); // MSVC gets confused if sizeof...(T) == 0
|
||||
return std::array<field_view, sizeof...(T)>{{to_field(std::get<I>(t))...}};
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
std::array<field_view, sizeof...(T)> tuple_to_array(const std::tuple<T...>& t) noexcept
|
||||
{
|
||||
return tuple_to_array_impl(t, mp11::make_index_sequence<sizeof...(T)>());
|
||||
}
|
||||
|
||||
struct query_request_getter
|
||||
{
|
||||
any_execution_request value;
|
||||
any_execution_request get() const noexcept { return value; }
|
||||
};
|
||||
inline query_request_getter make_request_getter(string_view q, std::vector<field_view>&) noexcept
|
||||
{
|
||||
return query_request_getter{q};
|
||||
}
|
||||
|
||||
struct stmt_it_request_getter
|
||||
{
|
||||
statement stmt;
|
||||
span<const field_view> params; // Points into the connection state's shared fields
|
||||
|
||||
any_execution_request get() const noexcept { return any_execution_request(stmt, params); }
|
||||
};
|
||||
|
||||
template <class FieldViewFwdIterator>
|
||||
inline stmt_it_request_getter make_request_getter(
|
||||
const bound_statement_iterator_range<FieldViewFwdIterator>& req,
|
||||
std::vector<field_view>& shared_fields
|
||||
)
|
||||
{
|
||||
auto& impl = access::get_impl(req);
|
||||
shared_fields.assign(impl.first, impl.last);
|
||||
return {impl.stmt, shared_fields};
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
struct stmt_tuple_request_getter
|
||||
{
|
||||
statement stmt;
|
||||
std::array<field_view, N> params;
|
||||
|
||||
any_execution_request get() const noexcept { return any_execution_request(stmt, params); }
|
||||
};
|
||||
template <class WritableFieldTuple>
|
||||
stmt_tuple_request_getter<std::tuple_size<WritableFieldTuple>::value>
|
||||
make_request_getter(const bound_statement_tuple<WritableFieldTuple>& req, std::vector<field_view>&)
|
||||
{
|
||||
auto& impl = access::get_impl(req);
|
||||
return {impl.stmt, tuple_to_array(impl.params)};
|
||||
}
|
||||
|
||||
//
|
||||
// helpers to run algos
|
||||
//
|
||||
|
||||
template <class AlgoParams>
|
||||
using has_void_result = std::is_same<typename AlgoParams::result_type, void>;
|
||||
|
||||
@ -185,16 +118,27 @@ struct generic_algo_handler
|
||||
connection_state* st;
|
||||
};
|
||||
|
||||
// Note: async_initiate args be, at least:
|
||||
// 1. a diagnostics*
|
||||
// 2. a (possibly smart) pointer to an I/O object
|
||||
// 3. everything else
|
||||
// This uniform structure allows to write completion tokens with extra functionality
|
||||
// Note: the 1st async_initiate arg should be a diagnostics*,
|
||||
// so completion tokens knowing how Boost.MySQL work can operate
|
||||
class connection_impl
|
||||
{
|
||||
std::unique_ptr<engine> engine_;
|
||||
std::unique_ptr<connection_state, connection_state_deleter> st_;
|
||||
|
||||
// Helper for execution requests
|
||||
template <class T>
|
||||
static auto make_request(T&& input, connection_state& st)
|
||||
-> decltype(execution_request_traits<typename std::decay<T>::type>::make_request(
|
||||
std::forward<T>(input),
|
||||
get_shared_fields(st)
|
||||
))
|
||||
{
|
||||
return execution_request_traits<typename std::decay<T>::type>::make_request(
|
||||
std::forward<T>(input),
|
||||
get_shared_fields(st)
|
||||
);
|
||||
}
|
||||
|
||||
// Generic algorithm
|
||||
template <class AlgoParams>
|
||||
typename AlgoParams::result_type run_impl(
|
||||
@ -330,15 +274,14 @@ class connection_impl
|
||||
diagnostics* diag,
|
||||
engine* eng,
|
||||
connection_state* st,
|
||||
const ExecutionRequest& req,
|
||||
ExecutionRequest&& req,
|
||||
execution_processor* proc
|
||||
)
|
||||
{
|
||||
auto getter = make_request_getter(req, get_shared_fields(*st));
|
||||
async_run_impl(
|
||||
*eng,
|
||||
*st,
|
||||
execute_algo_params{getter.get(), proc},
|
||||
execute_algo_params{make_request(std::forward<ExecutionRequest>(req), *st), proc},
|
||||
*diag,
|
||||
std::forward<Handler>(handler)
|
||||
);
|
||||
@ -354,15 +297,14 @@ class connection_impl
|
||||
diagnostics* diag,
|
||||
engine* eng,
|
||||
connection_state* st,
|
||||
const ExecutionRequest& req,
|
||||
ExecutionRequest&& req,
|
||||
execution_processor* proc
|
||||
)
|
||||
{
|
||||
auto getter = make_request_getter(req, get_shared_fields(*st));
|
||||
async_run_impl(
|
||||
*eng,
|
||||
*st,
|
||||
start_execution_algo_params{getter.get(), proc},
|
||||
start_execution_algo_params{make_request(std::forward<ExecutionRequest>(req), *st), proc},
|
||||
*diag,
|
||||
std::forward<Handler>(handler)
|
||||
);
|
||||
@ -499,10 +441,16 @@ public:
|
||||
|
||||
// Execute
|
||||
template <class ExecutionRequest, class ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
void execute(ExecutionRequest&& req, ResultsType& result, error_code& err, diagnostics& diag)
|
||||
{
|
||||
auto getter = make_request_getter(req, get_shared_fields(*st_));
|
||||
run(execute_algo_params{getter.get(), &access::get_impl(result).get_interface()}, err, diag);
|
||||
run(
|
||||
execute_algo_params{
|
||||
make_request(std::forward<ExecutionRequest>(req), *st_),
|
||||
&access::get_impl(result).get_interface()
|
||||
},
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class ExecutionRequest, class ResultsType, class CompletionToken>
|
||||
@ -536,14 +484,20 @@ public:
|
||||
// Start execution
|
||||
template <class ExecutionRequest, class ExecutionStateType>
|
||||
void start_execution(
|
||||
const ExecutionRequest& req,
|
||||
ExecutionRequest&& req,
|
||||
ExecutionStateType& exec_st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto getter = make_request_getter(req, get_shared_fields(*st_));
|
||||
run(start_execution_algo_params{getter.get(), &access::get_impl(exec_st).get_interface()}, err, diag);
|
||||
run(
|
||||
start_execution_algo_params{
|
||||
make_request(std::forward<ExecutionRequest>(req), *st_),
|
||||
&access::get_impl(exec_st).get_interface()
|
||||
},
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class ExecutionRequest, class ExecutionStateType, class CompletionToken>
|
||||
|
@ -8,9 +8,9 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_CONCEPTS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_CONCEPTS_HPP
|
||||
|
||||
#include <boost/mysql/statement.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/any_execution_request.hpp>
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
@ -61,33 +61,12 @@ template <class T>
|
||||
concept results_type = std::is_same_v<T, results> || is_static_results<T>::value;
|
||||
|
||||
// Execution request
|
||||
template <class T>
|
||||
struct is_bound_statement_tuple : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_bound_statement_tuple<bound_statement_tuple<T>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_bound_statement_range : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_bound_statement_range<bound_statement_iterator_range<T>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_execution_request
|
||||
{
|
||||
using without_cvref = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
static constexpr bool value = std::is_convertible<T, string_view>::value ||
|
||||
is_bound_statement_tuple<without_cvref>::value ||
|
||||
is_bound_statement_range<without_cvref>::value;
|
||||
static constexpr bool value = !std::is_base_of<
|
||||
no_execution_request_traits,
|
||||
execution_request_traits<typename std::decay<T>::type>>::value;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
@ -8,6 +8,7 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_FORMAT_SQL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_FORMAT_SQL_HPP
|
||||
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
@ -30,6 +31,7 @@ struct formatter;
|
||||
|
||||
class format_context_base;
|
||||
class formattable_ref;
|
||||
class format_arg;
|
||||
|
||||
namespace detail {
|
||||
|
||||
@ -157,6 +159,9 @@ struct formattable_ref_impl
|
||||
template <class T>
|
||||
formattable_ref_impl make_formattable_ref(T&& v);
|
||||
|
||||
BOOST_MYSQL_DECL
|
||||
void vformat_sql_to(format_context_base& ctx, constant_string_view format_str, span<const format_arg> args);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
@ -555,7 +555,14 @@ struct formatter<format_sequence_view<It, Sentinel, FormatFn>>
|
||||
* in `args` (there aren't enough arguments or a named argument is not found).
|
||||
*/
|
||||
template <BOOST_MYSQL_FORMATTABLE... Formattable>
|
||||
void format_sql_to(format_context_base& ctx, constant_string_view format_str, Formattable&&... args);
|
||||
void format_sql_to(format_context_base& ctx, constant_string_view format_str, Formattable&&... args)
|
||||
{
|
||||
std::initializer_list<format_arg> args_il{
|
||||
{string_view(), std::forward<Formattable>(args)}
|
||||
...
|
||||
};
|
||||
detail::vformat_sql_to(ctx, format_str, args_il);
|
||||
}
|
||||
|
||||
/**
|
||||
* \copydoc format_sql_to
|
||||
@ -563,12 +570,14 @@ void format_sql_to(format_context_base& ctx, constant_string_view format_str, Fo
|
||||
* \n
|
||||
* This overload allows using named arguments.
|
||||
*/
|
||||
BOOST_MYSQL_DECL
|
||||
void format_sql_to(
|
||||
inline void format_sql_to(
|
||||
format_context_base& ctx,
|
||||
constant_string_view format_str,
|
||||
std::initializer_list<format_arg> args
|
||||
);
|
||||
)
|
||||
{
|
||||
detail::vformat_sql_to(ctx, format_str, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief (EXPERIMENTAL) Composes a SQL query client-side.
|
||||
|
@ -158,20 +158,6 @@ boost::mysql::detail::formattable_ref_impl boost::mysql::detail::make_formattabl
|
||||
return make_formattable_ref_writable(std::forward<T>(v), is_writable_field_ref<T>());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FORMATTABLE... Formattable>
|
||||
void boost::mysql::format_sql_to(
|
||||
format_context_base& ctx,
|
||||
constant_string_view format_str,
|
||||
Formattable&&... args
|
||||
)
|
||||
{
|
||||
std::initializer_list<format_arg> args_il{
|
||||
{string_view(), std::forward<Formattable>(args)}
|
||||
...
|
||||
};
|
||||
format_sql_to(ctx, format_str, args_il);
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FORMATTABLE... Formattable>
|
||||
std::string boost::mysql::format_sql(
|
||||
format_options opts,
|
||||
|
@ -545,13 +545,13 @@ void boost::mysql::format_context_base::format_arg(detail::formattable_ref_impl
|
||||
}
|
||||
}
|
||||
|
||||
void boost::mysql::format_sql_to(
|
||||
void boost::mysql::detail::vformat_sql_to(
|
||||
format_context_base& ctx,
|
||||
constant_string_view format_str,
|
||||
std::initializer_list<format_arg> args
|
||||
span<const format_arg> args
|
||||
)
|
||||
{
|
||||
detail::format_state(ctx, {args.begin(), args.end()}).format(format_str.get());
|
||||
detail::format_state(ctx, args).format(format_str.get());
|
||||
}
|
||||
|
||||
std::string boost::mysql::format_sql(
|
||||
|
@ -54,19 +54,13 @@ class serialization_context
|
||||
|
||||
void append_to_buffer(span<const std::uint8_t> contents)
|
||||
{
|
||||
// Do nothing if we previously encountered an error
|
||||
if (err_)
|
||||
return;
|
||||
|
||||
// Check if the buffer has space for the given contents
|
||||
if (buffer_.size() + contents.size() > max_buffer_size_)
|
||||
{
|
||||
err_ = client_errc::max_buffer_size_exceeded;
|
||||
return;
|
||||
}
|
||||
add_error(client_errc::max_buffer_size_exceeded);
|
||||
|
||||
// Copy
|
||||
buffer_.insert(buffer_.end(), contents.begin(), contents.end());
|
||||
// Copy if there was no error
|
||||
if (!err_)
|
||||
buffer_.insert(buffer_.end(), contents.begin(), contents.end());
|
||||
}
|
||||
|
||||
void append_header() { append_to_buffer(std::array<std::uint8_t, frame_header_size>{}); }
|
||||
@ -138,6 +132,19 @@ public:
|
||||
// To be called by serialize() functions. Appends bytes to the buffer.
|
||||
void add(span<const std::uint8_t> content) { add_impl(content); }
|
||||
|
||||
// Make serialization_context compatible with output_string
|
||||
void append(const char* content, std::size_t size)
|
||||
{
|
||||
add({reinterpret_cast<const std::uint8_t*>(content), size});
|
||||
}
|
||||
|
||||
// Sets the error state
|
||||
void add_error(error_code ec)
|
||||
{
|
||||
if (!err_)
|
||||
err_ = ec;
|
||||
}
|
||||
|
||||
error_code error() const { return err_; }
|
||||
|
||||
// Write frame headers to an already serialized message with space for them
|
||||
|
@ -8,35 +8,54 @@
|
||||
#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_START_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_START_EXECUTION_HPP
|
||||
|
||||
#include <boost/mysql/character_set.hpp>
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/format_sql.hpp>
|
||||
|
||||
#include <boost/mysql/detail/algo_params.hpp>
|
||||
#include <boost/mysql/detail/any_execution_request.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/next_action.hpp>
|
||||
#include <boost/mysql/detail/output_string.hpp>
|
||||
#include <boost/mysql/detail/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/mysql/impl/internal/coroutine.hpp>
|
||||
#include <boost/mysql/impl/internal/protocol/impl/serialization_context.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/read_resultset_head.hpp>
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline error_code check_client_errors(const any_execution_request& req)
|
||||
// A serializable to generate the query in the write buffer without copies
|
||||
struct query_with_params
|
||||
{
|
||||
if (req.is_query)
|
||||
return error_code();
|
||||
return req.data.stmt.stmt.num_params() == req.data.stmt.params.size() ? error_code()
|
||||
: client_errc::wrong_num_params;
|
||||
}
|
||||
constant_string_view query;
|
||||
span<const format_arg> args;
|
||||
format_options opts;
|
||||
|
||||
inline resultset_encoding get_encoding(const any_execution_request& req)
|
||||
{
|
||||
return req.is_query ? resultset_encoding::text : resultset_encoding::binary;
|
||||
}
|
||||
void serialize(serialization_context& ctx) const
|
||||
{
|
||||
// Create a format context
|
||||
auto fmt_ctx = access::construct<format_context_base>(output_string_ref::create(ctx), opts);
|
||||
|
||||
// Serialize the query header
|
||||
ctx.add(0x03);
|
||||
|
||||
// Serialize the actual query
|
||||
vformat_sql_to(fmt_ctx, query, args);
|
||||
|
||||
// Check for errors
|
||||
ctx.add_error(fmt_ctx.error_state());
|
||||
}
|
||||
};
|
||||
|
||||
class start_execution_algo
|
||||
{
|
||||
@ -48,15 +67,49 @@ class start_execution_algo
|
||||
execution_processor& processor() { return read_head_st_.processor(); }
|
||||
diagnostics& diag() { return read_head_st_.diag(); }
|
||||
|
||||
static resultset_encoding get_encoding(any_execution_request::type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case any_execution_request::type_t::query:
|
||||
case any_execution_request::type_t::query_with_params: return resultset_encoding::text;
|
||||
case any_execution_request::type_t::stmt: return resultset_encoding::binary;
|
||||
default: BOOST_ASSERT(false); return resultset_encoding::text;
|
||||
}
|
||||
}
|
||||
|
||||
next_action write_query_with_params(
|
||||
connection_state_data& st,
|
||||
any_execution_request::data_t::query_with_params_t data
|
||||
)
|
||||
{
|
||||
// Determine format options
|
||||
if (st.current_charset.name == nullptr)
|
||||
{
|
||||
return error_code(client_errc::unknown_character_set);
|
||||
}
|
||||
format_options opts{st.current_charset, st.backslash_escapes};
|
||||
|
||||
// Write the request
|
||||
return st.write(query_with_params{data.query, data.args, opts}, seqnum());
|
||||
}
|
||||
|
||||
next_action write_stmt(connection_state_data& st, any_execution_request::data_t::stmt_t data)
|
||||
{
|
||||
if (data.num_params != data.params.size())
|
||||
return error_code(client_errc::wrong_num_params);
|
||||
return st.write(execute_stmt_command{data.stmt_id, data.params}, seqnum());
|
||||
}
|
||||
|
||||
next_action compose_request(connection_state_data& st)
|
||||
{
|
||||
if (req_.is_query)
|
||||
switch (req_.type)
|
||||
{
|
||||
return st.write(query_command{req_.data.query}, seqnum());
|
||||
}
|
||||
else
|
||||
{
|
||||
return st.write(execute_stmt_command{req_.data.stmt.stmt.id(), req_.data.stmt.params}, seqnum());
|
||||
case any_execution_request::type_t::query: return st.write(query_command{req_.data.query}, seqnum());
|
||||
case any_execution_request::type_t::query_with_params:
|
||||
return write_query_with_params(st, req_.data.query_with_params);
|
||||
case any_execution_request::type_t::stmt: return write_stmt(st, req_.data.stmt);
|
||||
default: BOOST_ASSERT(false); return next_action();
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,17 +130,11 @@ public:
|
||||
// Clear diagnostics
|
||||
diag().clear();
|
||||
|
||||
// Check for errors
|
||||
ec = check_client_errors(req_);
|
||||
if (ec)
|
||||
return ec;
|
||||
|
||||
// Reset the processor
|
||||
processor().reset(get_encoding(req_), st.meta_mode);
|
||||
processor().reset(get_encoding(req_.type), st.meta_mode);
|
||||
|
||||
// Send the execution request
|
||||
BOOST_MYSQL_YIELD(resume_point_, 1, compose_request(st))
|
||||
|
||||
if (ec)
|
||||
return ec;
|
||||
|
||||
@ -105,4 +152,4 @@ public:
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP_ */
|
||||
#endif
|
||||
|
@ -10,11 +10,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/access.hpp>
|
||||
#include <boost/mysql/detail/any_execution_request.hpp>
|
||||
#include <boost/mysql/detail/writable_field_traits.hpp>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple>
|
||||
class boost::mysql::bound_statement_tuple
|
||||
@ -79,4 +86,60 @@ boost::mysql::bound_statement_iterator_range<FieldViewFwdIterator> boost::mysql:
|
||||
return bound_statement_iterator_range<FieldViewFwdIterator>(*this, first, last);
|
||||
}
|
||||
|
||||
// Execution request traits
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Tuple
|
||||
template <std::size_t N>
|
||||
struct stmt_tuple_request_proxy
|
||||
{
|
||||
statement stmt;
|
||||
std::array<field_view, N> params;
|
||||
|
||||
operator any_execution_request() const
|
||||
{
|
||||
return any_execution_request({stmt.id(), static_cast<std::uint16_t>(stmt.num_params()), params});
|
||||
}
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct execution_request_traits<bound_statement_tuple<std::tuple<T...>>>
|
||||
{
|
||||
template <std::size_t... I>
|
||||
static std::array<field_view, sizeof...(T)> tuple_to_array(const std::tuple<T...>& t, mp11::index_sequence<I...>)
|
||||
{
|
||||
boost::ignore_unused(t); // MSVC gets confused if sizeof...(T) == 0
|
||||
return {{to_field(std::get<I>(t))...}};
|
||||
}
|
||||
|
||||
static stmt_tuple_request_proxy<sizeof...(T)> make_request(const bound_statement_tuple<std::tuple<T...>>& input, std::vector<field_view>&)
|
||||
{
|
||||
auto& impl = access::get_impl(input);
|
||||
return {impl.stmt, tuple_to_array(impl.params, mp11::make_index_sequence<sizeof...(T)>())};
|
||||
}
|
||||
};
|
||||
|
||||
// Iterator range
|
||||
template <class FieldViewFwdIterator>
|
||||
struct execution_request_traits<bound_statement_iterator_range<FieldViewFwdIterator>>
|
||||
{
|
||||
static any_execution_request make_request(
|
||||
const bound_statement_iterator_range<FieldViewFwdIterator>& input,
|
||||
std::vector<field_view>& shared_fields
|
||||
)
|
||||
{
|
||||
auto& impl = access::get_impl(input);
|
||||
shared_fields.assign(impl.first, impl.last);
|
||||
return any_execution_request(
|
||||
{impl.stmt.id(), static_cast<std::uint16_t>(impl.stmt.num_params()), shared_fields}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
||||
|
85
include/boost/mysql/impl/with_params.hpp
Normal file
85
include/boost/mysql/impl/with_params.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// 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_WITH_PARAMS_HPP
|
||||
#define BOOST_MYSQL_IMPL_WITH_PARAMS_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/format_sql.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/mysql/detail/any_execution_request.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
// Execution request traits
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <std::size_t N>
|
||||
struct with_params_proxy
|
||||
{
|
||||
constant_string_view query;
|
||||
std::array<format_arg, N> args;
|
||||
|
||||
operator detail::any_execution_request() const { return any_execution_request({query, args}); }
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct execution_request_traits<with_params_t<T...>>
|
||||
{
|
||||
template <class WithParamsType, std::size_t... I>
|
||||
static with_params_proxy<sizeof...(T)> make_request_impl(WithParamsType&& input, mp11::index_sequence<I...>)
|
||||
{
|
||||
boost::ignore_unused(input); // MSVC gets confused for tuples of size 0
|
||||
// clang-format off
|
||||
return {
|
||||
input.query,
|
||||
{{
|
||||
{
|
||||
string_view(),
|
||||
formattable_ref(std::get<I>(std::forward<WithParamsType>(input).args))
|
||||
}...
|
||||
}}
|
||||
};
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Allow the value category of the object to be deduced
|
||||
template <class WithParamsType>
|
||||
static with_params_proxy<sizeof...(T)> make_request(WithParamsType&& input, std::vector<field_view>&)
|
||||
{
|
||||
return make_request_impl(
|
||||
std::forward<WithParamsType>(input),
|
||||
mp11::make_index_sequence<sizeof...(T)>()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Old MSVCs fail to process the above when sizeof...(T) is zero
|
||||
template <>
|
||||
struct execution_request_traits<with_params_t<>>
|
||||
{
|
||||
static any_execution_request make_request(with_params_t<> input, std::vector<field_view>&)
|
||||
{
|
||||
return any_execution_request({input.query, span<format_arg>{}});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
120
include/boost/mysql/with_params.hpp
Normal file
120
include/boost/mysql/with_params.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
//
|
||||
// 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_WITH_PARAMS_HPP
|
||||
#define BOOST_MYSQL_WITH_PARAMS_HPP
|
||||
|
||||
#include <boost/mysql/constant_string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/format_sql.hpp>
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief Type trait that applies the transformation performed by `std::make_tuple` to a single element.
|
||||
* \details
|
||||
* For example: \n
|
||||
* - `make_tuple_element_t<int>` yields `int` \n
|
||||
* - `make_tuple_element_t<const int&>` yields `int` \n
|
||||
* - `make_tuple_element_t<std::reference_wrapper<int>>` yields `int&` \n
|
||||
* \n
|
||||
* Consult the <a href="https://en.cppreference.com/w/cpp/utility/tuple/make_tuple">`std::make_tuple`</a> docs
|
||||
* for more info.
|
||||
*/
|
||||
template <class T>
|
||||
using make_tuple_element_t = typename std::tuple_element<0, decltype(std::make_tuple(std::declval<T&&>()))>::
|
||||
type;
|
||||
|
||||
/**
|
||||
* \brief A query format string and format arguments that can be executed.
|
||||
* \details
|
||||
* Contains a query with placeholders (i.e. `{}`) and a set of parameters to
|
||||
* expand such placeholders. Satisfies `ExecutionRequest` and can thus be passed
|
||||
* to \ref any_connection::execute, \ref any_connection::start_execution and its
|
||||
* async counterparts.
|
||||
* \n
|
||||
* When executed, client-side SQL formatting is invoked
|
||||
* to expand the provided query with the supplied parameters. The resulting query is then sent to
|
||||
* the server for execution. Formally, given a `conn` variable of \ref any_connection type,
|
||||
* the query is generated as if the following was called:
|
||||
* ```
|
||||
* format_sql(
|
||||
* this->query,
|
||||
* conn.format_opts().value(),
|
||||
* std::get<i>(this->args)... // for i in [0, sizeof...(Formattable))
|
||||
* );
|
||||
* ```
|
||||
* \n
|
||||
* Objects of this type are usually created using \ref with_params, which
|
||||
* creates `args` by calling `std::make_tuple`.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The format string `query` is stored as a view, as a compile-time string should be used in most cases.
|
||||
* When using \ref with_params, `args` will usually contain copies of the passed parameters
|
||||
* (as per <a href="https://en.cppreference.com/w/cpp/utility/tuple/make_tuple">`std::make_tuple`</a>),
|
||||
* which is safe even when using async functions with deferred completion tokens.
|
||||
* You may disable such copies using `std::ref`, as you would when using `std::make_tuple`.
|
||||
*
|
||||
* \par Errors
|
||||
* When passed to \ref any_connection::execute, \ref any_connection::start_execution or
|
||||
* its async counterparts, in addition to the usual network and server-generated errors,
|
||||
* `with_params_t` may generate the following errors: \n
|
||||
* - Any errors generated by \ref format_sql. This includes errors due to invalid format
|
||||
* strings and unformattable arguments (e.g. invalid UTF-8).
|
||||
* - \ref client_errc::unknown_character_set if the connection does not know the
|
||||
* character set it's using when the query is executed (i.e. \ref any_connection::current_character_set
|
||||
* would return an error.
|
||||
*/
|
||||
template <BOOST_MYSQL_FORMATTABLE... Formattable>
|
||||
struct with_params_t
|
||||
{
|
||||
/// The query to be expanded and executed, which may contain `{}` placeholders.
|
||||
constant_string_view query;
|
||||
|
||||
/// The arguments to use to expand the query.
|
||||
std::tuple<Formattable...> args;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Creates a query with parameters (client-side SQL formatting) that can be executed.
|
||||
* \details
|
||||
* Creates a \ref with_params_t object by packing the supplied arguments into a tuple,
|
||||
* calling <a href="https://en.cppreference.com/w/cpp/utility/tuple/make_tuple">`std::make_tuple`</a>.
|
||||
* As per `std::make_tuple`, parameters will be decay-copied into the resulting object.
|
||||
* This behavior can be disabled by passing `std::reference_wrapper` objects, which are
|
||||
* transformed into references.
|
||||
* \n
|
||||
* This function does not inspect the supplied query string and arguments.
|
||||
* Errors like missing format arguments are detected when the resulting object is executed.
|
||||
* This function does not involve communication with the server.
|
||||
* \n
|
||||
* The passed `args` must either satisfy `Formattable`, or be `std::reference_wrapper<T>`
|
||||
* with `T` satisfying `Formattable`.
|
||||
* \n
|
||||
* See \ref with_params_t for details on how the execution request works.
|
||||
* \n
|
||||
* \par Exception safety
|
||||
* Strong guarantee. Any exception thrown when copying `args` will be propagated.
|
||||
* \n
|
||||
*/
|
||||
template <class... FormattableOrRefWrapper>
|
||||
auto with_params(constant_string_view query, FormattableOrRefWrapper&&... args)
|
||||
-> with_params_t<make_tuple_element_t<FormattableOrRefWrapper>...>
|
||||
{
|
||||
return {query, std::make_tuple(std::forward<FormattableOrRefWrapper>(args)...)};
|
||||
}
|
||||
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/impl/with_params.hpp>
|
||||
|
||||
#endif
|
@ -7,20 +7,32 @@
|
||||
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/field.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "test_common/create_basic.hpp"
|
||||
#include "test_common/has_ranges.hpp"
|
||||
#include "test_common/network_result.hpp"
|
||||
#include "test_integration/any_connection_fixture.hpp"
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
using namespace boost::mysql;
|
||||
using namespace boost::mysql::test;
|
||||
using boost::test_tools::per_element;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
// Cover all possible execution requests for execute() and async_execute()
|
||||
|
||||
@ -39,7 +51,11 @@ BOOST_FIXTURE_TEST_CASE(query, any_connection_fixture)
|
||||
conn.async_execute("SELECT 1", r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(1, 1), per_element());
|
||||
|
||||
// start execution
|
||||
// types convertible to string_view work
|
||||
conn.async_execute(std::string("SELECT 1"), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(1, 1), per_element());
|
||||
|
||||
// spotcheck: start execution with a text query works
|
||||
conn.async_start_execution("SELECT 1", st, as_netresult).validate_no_error();
|
||||
auto rws = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST(rws == makerows(1, 1), per_element());
|
||||
@ -47,37 +63,38 @@ BOOST_FIXTURE_TEST_CASE(query, any_connection_fixture)
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(stmt_tuple, any_connection_fixture)
|
||||
{
|
||||
// Also verifies that tuples correctly apply the writable field transformation
|
||||
// Setup
|
||||
connect();
|
||||
results r;
|
||||
execution_state st;
|
||||
auto stmt = conn.async_prepare_statement("SELECT ?", as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(stmt.num_params() == 1u);
|
||||
auto stmt = conn.async_prepare_statement("SELECT ?, ?", as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(stmt.num_params() == 2u);
|
||||
|
||||
// execute
|
||||
conn.async_execute(stmt.bind("42"), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(1, "42"), per_element());
|
||||
conn.async_execute(stmt.bind("42", boost::optional<int>(13)), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, "42", 13), per_element());
|
||||
|
||||
// start execution
|
||||
conn.async_start_execution(stmt.bind(90), st, as_netresult).validate_no_error();
|
||||
// references work
|
||||
std::string s = "abcdef";
|
||||
auto op = conn.async_execute(stmt.bind(std::ref(s), 21), r, asio::deferred);
|
||||
s = "opqrs";
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, "opqrs", 21), per_element());
|
||||
|
||||
// spotcheck: start execution with tuples works
|
||||
conn.async_start_execution(stmt.bind("abc", boost::optional<int>()), st, as_netresult)
|
||||
.validate_no_error();
|
||||
auto rws = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST(rws == makerows(1, 90), per_element());
|
||||
}
|
||||
BOOST_TEST(rws == makerows(2, "abc", nullptr), per_element());
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(stmt_tuple_error, any_connection_fixture)
|
||||
{
|
||||
// Setup
|
||||
connect();
|
||||
results r;
|
||||
execution_state st;
|
||||
auto stmt = conn.async_prepare_statement("SELECT ?", as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(stmt.num_params() == 1u);
|
||||
// spotcheck: errors correctly detected
|
||||
conn.async_execute(stmt.bind("42"), r, as_netresult).validate_error(client_errc::wrong_num_params);
|
||||
|
||||
// execute
|
||||
conn.async_execute(stmt.bind("42", 200), r, as_netresult).validate_error(client_errc::wrong_num_params);
|
||||
|
||||
// start execution
|
||||
conn.async_start_execution(stmt.bind(), st, as_netresult).validate_error(client_errc::wrong_num_params);
|
||||
// spotcheck: lvalues work
|
||||
const auto bound_stmt = stmt.bind("42", boost::optional<int>());
|
||||
conn.async_execute(bound_stmt, r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, "42", nullptr), per_element());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(stmt_range, any_connection_fixture)
|
||||
@ -94,29 +111,77 @@ BOOST_FIXTURE_TEST_CASE(stmt_range, any_connection_fixture)
|
||||
conn.async_execute(stmt.bind(params.begin(), params.end()), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, 42, "abc"), per_element());
|
||||
|
||||
// start execution
|
||||
// spotcheck: statement with ranges work with start execution
|
||||
conn.async_start_execution(stmt.bind(params.begin(), params.end()), st, as_netresult).validate_no_error();
|
||||
auto rws = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST(rws == makerows(2, 42, "abc"), per_element());
|
||||
|
||||
// Regression check: executing with a type convertible (but not equal) to field_view works
|
||||
const std::vector<field> owning_params{field_view(50), field_view("luv")};
|
||||
conn.async_execute(stmt.bind(owning_params.begin(), owning_params.end()), r, as_netresult)
|
||||
.validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, 50, "luv"), per_element());
|
||||
|
||||
// Spotcheck: errors detected
|
||||
auto too_few_params = make_fv_arr(1);
|
||||
conn.async_execute(stmt.bind(too_few_params.begin(), too_few_params.end()), r, as_netresult)
|
||||
.validate_error(client_errc::wrong_num_params);
|
||||
|
||||
// Spotchecks: lvalues work
|
||||
auto bound_stmt = stmt.bind(params.begin(), params.end());
|
||||
conn.async_execute(bound_stmt, r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, 42, "abc"), per_element());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(stmt_range_error, any_connection_fixture)
|
||||
BOOST_FIXTURE_TEST_CASE(with_params_, any_connection_fixture)
|
||||
{
|
||||
// Setup
|
||||
connect();
|
||||
results r;
|
||||
execution_state st;
|
||||
std::forward_list<field_view> params{field_view(42)};
|
||||
auto stmt = conn.async_prepare_statement("SELECT ?, ?", as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(stmt.num_params() == 2u);
|
||||
|
||||
// execute
|
||||
conn.async_execute(stmt.bind(params.begin(), params.end()), r, as_netresult)
|
||||
.validate_error(client_errc::wrong_num_params);
|
||||
conn.async_execute(with_params("SELECT {}, {}", 42, "abc"), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, 42, "abc"), per_element());
|
||||
|
||||
// start execution
|
||||
conn.async_start_execution(stmt.bind(params.begin(), params.end()), st, as_netresult)
|
||||
.validate_error(client_errc::wrong_num_params);
|
||||
// spotcheck: can be used with start_execution
|
||||
conn.async_start_execution(with_params("SELECT {}, {}", 42, "abc"), st, as_netresult).validate_no_error();
|
||||
auto rws = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST(rws == makerows(2, 42, "abc"), per_element());
|
||||
|
||||
// references work
|
||||
std::string s = "abcdef";
|
||||
auto op = conn.async_execute(with_params("SELECT {}, {}", 42, std::ref(s)), r, asio::deferred);
|
||||
s = "opqrs";
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, 42, "opqrs"), per_element());
|
||||
|
||||
// Queries without parameters work
|
||||
conn.async_execute(with_params("SELECT '{{}}'"), r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(1, "{}"), per_element());
|
||||
|
||||
// lvalues work
|
||||
const auto req = with_params("SELECT {}, {}", "42", boost::optional<int>(100));
|
||||
conn.async_execute(req, r, as_netresult).validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(2, "42", 100), per_element());
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_RANGES
|
||||
// Regression check: mutable ranges work
|
||||
std::vector<int> nums{2, 5, 10, 20};
|
||||
auto is_even = [](int i) { return i % 2 == 0; };
|
||||
conn.async_execute(with_params("SELECT {}", std::ranges::views::filter(nums, is_even)), r, as_netresult)
|
||||
.validate_no_error();
|
||||
BOOST_TEST(r.rows() == makerows(3, 2, 10, 20), per_element());
|
||||
#endif
|
||||
|
||||
// Error: unformattable values
|
||||
conn.async_execute(with_params("SELECT {}", "bad\xffutf8"), r, as_netresult)
|
||||
.validate_error(client_errc::invalid_encoding);
|
||||
|
||||
// Error: unknown charset
|
||||
conn.async_reset_connection(as_netresult).validate_no_error();
|
||||
conn.async_execute(with_params("SELECT {}", 42), r, as_netresult)
|
||||
.validate_error(client_errc::unknown_character_set);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
@ -24,6 +25,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "test_common/has_ranges.hpp"
|
||||
#include "test_common/printing.hpp"
|
||||
@ -70,7 +72,10 @@ std::string compose_select_query(
|
||||
boost::mysql::format_sql_to(ctx, " LIMIT {}", *limit);
|
||||
}
|
||||
|
||||
// Retrieve the generated query string
|
||||
// Retrieve the generated query string.
|
||||
// get() returns a boost::system::result<std::string> that
|
||||
// contains an error if any of the format operations failed.
|
||||
// Calling value() will throw on error, like format_sql does
|
||||
return std::move(ctx).get().value();
|
||||
}
|
||||
//]
|
||||
@ -129,138 +134,98 @@ BOOST_AUTO_TEST_CASE(section_sql_formatting)
|
||||
{
|
||||
//[sql_formatting_simple
|
||||
std::string employee_name = get_name(); // employee_name is an untrusted string
|
||||
|
||||
// Compose the SQL query in the client
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id, salary FROM employee WHERE last_name = {}",
|
||||
employee_name
|
||||
);
|
||||
|
||||
// If employee_name is "John", query now contains:
|
||||
// "SELECT id, salary FROM employee WHERE last_name = 'John'"
|
||||
// If employee_name contains quotes, they will be escaped as required
|
||||
|
||||
// Execute the generated query as usual
|
||||
results result;
|
||||
conn.execute(query, result);
|
||||
//]
|
||||
|
||||
BOOST_TEST(query == "SELECT id, salary FROM employee WHERE last_name = 'John'");
|
||||
// Expand the query and execute it. The expansion happens client-side.
|
||||
// If employee_name is "John", the executed query would be:
|
||||
// "SELECT id, salary FROM employee WHERE last_name = 'John'"
|
||||
conn.execute(
|
||||
with_params("SELECT id, salary FROM employee WHERE last_name = {}", employee_name),
|
||||
result
|
||||
);
|
||||
//]
|
||||
}
|
||||
{
|
||||
//[sql_formatting_other_scalars
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id FROM employee WHERE salary > {}",
|
||||
42000
|
||||
);
|
||||
|
||||
BOOST_TEST(query == "SELECT id FROM employee WHERE salary > 42000");
|
||||
// Will execute "SELECT id FROM employee WHERE salary > 42000"
|
||||
results result;
|
||||
conn.execute(with_params("SELECT id FROM employee WHERE salary > {}", 42000), result);
|
||||
//]
|
||||
|
||||
conn.execute(query, r);
|
||||
}
|
||||
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
|
||||
{
|
||||
//[sql_formatting_optionals
|
||||
std::optional<std::int64_t> salary; // get salary from a possibly untrusted source
|
||||
results result;
|
||||
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"UPDATE employee SET salary = {} WHERE id = {}",
|
||||
salary,
|
||||
1
|
||||
);
|
||||
|
||||
// Depending on whether salary has a value or not, generates:
|
||||
// UPDATE employee SET salary = 42000 WHERE id = 1
|
||||
// UPDATE employee SET salary = NULL WHERE id = 1
|
||||
// Depending on whether salary has a value or not, executes:
|
||||
// "UPDATE employee SET salary = 42000 WHERE id = 1"
|
||||
// "UPDATE employee SET salary = NULL WHERE id = 1"
|
||||
conn.execute(with_params("UPDATE employee SET salary = {} WHERE id = {}", salary, 1), result);
|
||||
//]
|
||||
|
||||
BOOST_TEST(query == "UPDATE employee SET salary = NULL WHERE id = 1");
|
||||
conn.execute(query, r);
|
||||
}
|
||||
#endif
|
||||
{
|
||||
//[sql_formatting_ranges
|
||||
results result;
|
||||
std::vector<long> ids{1, 5, 20};
|
||||
std::string query = format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT * FROM employee WHERE id IN ({})",
|
||||
ids
|
||||
);
|
||||
BOOST_TEST(query == "SELECT * FROM employee WHERE id IN (1, 5, 20)");
|
||||
|
||||
// Executes "SELECT * FROM employee WHERE id IN (1, 5, 20)"
|
||||
conn.execute(with_params("SELECT * FROM employee WHERE id IN ({})", ids), result);
|
||||
//]
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_manual_indices
|
||||
// Recall that you need to set connect_params::multi_queries to true when connecting
|
||||
// before running semicolon-separated queries.
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"UPDATE employee SET first_name = {1} WHERE id = {0}; SELECT * FROM employee WHERE id = {0}",
|
||||
42,
|
||||
"John"
|
||||
);
|
||||
|
||||
BOOST_TEST(
|
||||
query ==
|
||||
"UPDATE employee SET first_name = 'John' WHERE id = 42; SELECT * FROM employee WHERE id = 42"
|
||||
// before running semicolon-separated queries. Executes:
|
||||
// "UPDATE employee SET first_name = 'John' WHERE id = 42; SELECT * FROM employee WHERE id = 42"
|
||||
results result;
|
||||
conn.execute(
|
||||
with_params(
|
||||
"UPDATE employee SET first_name = {1} WHERE id = {0}; SELECT * FROM employee WHERE id = {0}",
|
||||
42,
|
||||
"John"
|
||||
),
|
||||
result
|
||||
);
|
||||
//]
|
||||
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
// clang-format off
|
||||
//[sql_formatting_named_args
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"UPDATE employee SET first_name = {name} WHERE id = {id}; SELECT * FROM employee WHERE id = {id}",
|
||||
{
|
||||
{"id", 42 },
|
||||
{"name", "John"}
|
||||
}
|
||||
);
|
||||
//<-
|
||||
// clang-format on
|
||||
//->
|
||||
|
||||
BOOST_TEST(
|
||||
query ==
|
||||
"UPDATE employee SET first_name = 'John' WHERE id = 42; SELECT * FROM employee WHERE id = 42"
|
||||
);
|
||||
//[sql_formatting_invalid_encoding
|
||||
try
|
||||
{
|
||||
// If the connection is using UTF-8 (the default), this will throw an error,
|
||||
// because the string to be formatted is not valid UTF-8.
|
||||
// The query never reaches the server.
|
||||
results result;
|
||||
conn.execute(with_params("SELECT {}", "bad\xff UTF-8"), result);
|
||||
//<-
|
||||
BOOST_TEST(false);
|
||||
//->
|
||||
}
|
||||
catch (const boost::system::system_error& err)
|
||||
{
|
||||
BOOST_TEST(err.code() == boost::mysql::client_errc::invalid_encoding);
|
||||
}
|
||||
//]
|
||||
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_specifiers
|
||||
//[sql_formatting_format_sql
|
||||
// Compose the SQL query without executing it.
|
||||
// format_opts returns a system::result<format_options>,
|
||||
// contains settings like the current character set.
|
||||
// If the connection is using an unknown character set, this will throw an error.
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id, last_name FROM employee ORDER BY {:i} DESC",
|
||||
"company_id"
|
||||
"SELECT id, salary FROM employee WHERE last_name = {}",
|
||||
"Doe"
|
||||
);
|
||||
|
||||
BOOST_TEST(query == "SELECT id, last_name FROM employee ORDER BY `company_id` DESC");
|
||||
BOOST_TEST(query == "SELECT id, salary FROM employee WHERE last_name = 'Doe'");
|
||||
//]
|
||||
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_specifiers_explicit_indices
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id, last_name FROM employee ORDER BY {0:i} DESC",
|
||||
"company_id"
|
||||
);
|
||||
//]
|
||||
|
||||
BOOST_TEST(query == "SELECT id, last_name FROM employee ORDER BY `company_id` DESC");
|
||||
conn.execute(query, r);
|
||||
}
|
||||
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
|
||||
{
|
||||
//[sql_formatting_incremental_use
|
||||
@ -330,22 +295,45 @@ BOOST_AUTO_TEST_CASE(section_sql_formatting)
|
||||
//]
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
try
|
||||
{
|
||||
//[sql_formatting_invalid_encoding
|
||||
// If the connection is using UTF-8 (the default), this will throw an error,
|
||||
// because the string to be formatted contains invalid UTF8.
|
||||
format_sql(conn.format_opts().value(), "SELECT {}", "bad\xff UTF-8");
|
||||
//]
|
||||
|
||||
BOOST_TEST(false);
|
||||
}
|
||||
catch (const boost::system::system_error& err)
|
||||
{
|
||||
BOOST_TEST(err.code() == boost::mysql::client_errc::invalid_encoding);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_specifiers
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id, last_name FROM employee ORDER BY {:i} DESC",
|
||||
"company_id"
|
||||
);
|
||||
|
||||
BOOST_TEST(query == "SELECT id, last_name FROM employee ORDER BY `company_id` DESC");
|
||||
//]
|
||||
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_specifiers_explicit_indices
|
||||
std::string query = boost::mysql::format_sql(
|
||||
conn.format_opts().value(),
|
||||
"SELECT id, last_name FROM employee ORDER BY {0:i} DESC",
|
||||
"company_id"
|
||||
);
|
||||
//]
|
||||
|
||||
BOOST_TEST(query == "SELECT id, last_name FROM employee ORDER BY `company_id` DESC");
|
||||
conn.execute(query, r);
|
||||
}
|
||||
{
|
||||
//[sql_formatting_empty_ranges
|
||||
// If ids.empty(), generates "SELECT * FROM employee WHERE id IN ()", which is a syntax error.
|
||||
// This is not a security issue for this query, but may be exploitable in more involved scenarios.
|
||||
// Queries involving only scalar values (as opposed to ranges) are not affected by this.
|
||||
// It is your responsibility to check for conditions like ids.empty(), as client-side SQL
|
||||
// formatting does not understand your queries.
|
||||
std::vector<int> ids;
|
||||
auto q = format_sql(conn.format_opts().value(), "SELECT * FROM employee WHERE id IN ({})", ids);
|
||||
//]
|
||||
BOOST_TEST(q == "SELECT * FROM employee WHERE id IN ()");
|
||||
}
|
||||
|
||||
{
|
||||
using namespace boost::mysql;
|
||||
using boost::optional;
|
||||
|
@ -79,6 +79,7 @@ add_executable(
|
||||
test/spotchecks/default_completion_tokens.cpp
|
||||
test/spotchecks/read_some_rows_static.cpp
|
||||
test/spotchecks/multifn.cpp
|
||||
test/spotchecks/execution_requests.cpp
|
||||
test/spotchecks/misc.cpp
|
||||
|
||||
test/format_sql/formattable.cpp
|
||||
|
@ -88,6 +88,7 @@ run
|
||||
test/spotchecks/default_completion_tokens.cpp
|
||||
test/spotchecks/read_some_rows_static.cpp
|
||||
test/spotchecks/multifn.cpp
|
||||
test/spotchecks/execution_requests.cpp
|
||||
test/spotchecks/misc.cpp
|
||||
|
||||
test/format_sql/formattable.cpp
|
||||
|
26
test/unit/include/test_unit/test_any_connection.hpp
Normal file
26
test/unit/include/test_unit/test_any_connection.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// 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_TEST_ANY_CONNECTION_HPP
|
||||
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_TEST_ANY_CONNECTION_HPP
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
any_connection create_test_any_connection(any_connection_params params = {});
|
||||
test_stream& get_stream(any_connection& conn);
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -5,8 +5,14 @@
|
||||
// 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/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/access.hpp>
|
||||
#include <boost/mysql/detail/engine.hpp>
|
||||
#include <boost/mysql/detail/engine_impl.hpp>
|
||||
#include <boost/mysql/detail/engine_stream_adaptor.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
@ -18,11 +24,13 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "test_common/buffer_concat.hpp"
|
||||
#include "test_common/tracker_executor.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
using namespace boost::mysql::test;
|
||||
@ -186,3 +194,18 @@ test_stream& boost::mysql::test::test_stream::add_break(std::size_t byte_num)
|
||||
read_break_offsets_.insert(byte_num);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// test_any_connection
|
||||
boost::mysql::any_connection boost::mysql::test::create_test_any_connection(any_connection_params params)
|
||||
{
|
||||
return any_connection(detail::access::construct<any_connection>(
|
||||
std::unique_ptr<detail::engine>(new detail::engine_impl<detail::engine_stream_adaptor<test_stream>>()
|
||||
),
|
||||
params
|
||||
));
|
||||
}
|
||||
|
||||
test_stream& boost::mysql::test::get_stream(any_connection& conn)
|
||||
{
|
||||
return detail::stream_from_engine<test_stream>(detail::access::get_impl(conn).get_engine());
|
||||
}
|
@ -5,6 +5,8 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
@ -69,6 +71,17 @@ static_assert(is_execution_request<bound_statement_iterator_range<field_view*>&&
|
||||
|
||||
static_assert(!is_execution_request<field_view*>::value, "");
|
||||
|
||||
// with_params
|
||||
static_assert(is_execution_request<with_params_t<>>::value, "");
|
||||
static_assert(is_execution_request<with_params_t<int>>::value, "");
|
||||
static_assert(is_execution_request<with_params_t<int, float>>::value, "");
|
||||
static_assert(is_execution_request<with_params_t<const std::string&, float>>::value, "");
|
||||
|
||||
static_assert(is_execution_request<with_params_t<int>&>::value, "");
|
||||
static_assert(is_execution_request<const with_params_t<int>&>::value, "");
|
||||
static_assert(is_execution_request<with_params_t<int>&&>::value, "");
|
||||
static_assert(is_execution_request<with_params_t<const std::string&>&&>::value, "");
|
||||
|
||||
// Other stuff
|
||||
static_assert(!is_execution_request<field_view>::value, "");
|
||||
static_assert(!is_execution_request<int>::value, "");
|
||||
|
@ -439,6 +439,38 @@ BOOST_AUTO_TEST_CASE(success_after_error)
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(add_error)
|
||||
{
|
||||
// Setup
|
||||
std::vector<std::uint8_t> buff;
|
||||
detail::serialization_context ctx(buff);
|
||||
|
||||
// Successfully add some data
|
||||
ctx.add(std::vector<std::uint8_t>{1, 2, 3, 4, 5});
|
||||
std::vector<std::uint8_t> expected{0, 0, 0, 0, 1, 2, 3, 4, 5};
|
||||
BOOST_TEST(ctx.error() == error_code());
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
|
||||
// Add an error
|
||||
ctx.add_error(client_errc::invalid_encoding);
|
||||
BOOST_TEST(ctx.error() == client_errc::invalid_encoding);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
|
||||
// Adding further data with the error set does nothing
|
||||
ctx.add(std::vector<std::uint8_t>{8, 9, 10});
|
||||
BOOST_TEST(ctx.error() == client_errc::invalid_encoding);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
|
||||
// Adding another error does nothing
|
||||
ctx.add_error(client_errc::protocol_value_error);
|
||||
BOOST_TEST(ctx.error() == client_errc::invalid_encoding);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
|
||||
ctx.add_error(error_code());
|
||||
BOOST_TEST(ctx.error() == client_errc::invalid_encoding);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff, expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include "test_unit/create_ok.hpp"
|
||||
#include "test_unit/create_ok_frame.hpp"
|
||||
#include "test_unit/create_row_message.hpp"
|
||||
#include "test_unit/create_statement.hpp"
|
||||
#include "test_unit/mock_execution_processor.hpp"
|
||||
#include "test_unit/printing.hpp"
|
||||
|
||||
@ -239,15 +238,11 @@ BOOST_AUTO_TEST_CASE(execute_success_rows)
|
||||
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
|
||||
execute_fixture fix(any_execution_request(stmt, params));
|
||||
execute_fixture fix(any_execution_request({std::uint32_t(1), std::uint16_t(2), params}));
|
||||
|
||||
// Run the algo. Nothing should be written to the server
|
||||
algo_test().check(fix, client_errc::wrong_num_params);
|
||||
|
||||
// We didn't modify the processor
|
||||
fix.proc.num_calls().validate();
|
||||
}
|
||||
|
||||
// Tests error on write, while reading head and while reading rows (error spotcheck)
|
||||
|
@ -5,6 +5,7 @@
|
||||
// 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/column_type.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
@ -19,6 +20,7 @@
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "test_common/check_meta.hpp"
|
||||
@ -27,7 +29,9 @@
|
||||
#include "test_unit/create_coldef_frame.hpp"
|
||||
#include "test_unit/create_frame.hpp"
|
||||
#include "test_unit/create_meta.hpp"
|
||||
#include "test_unit/create_statement.hpp"
|
||||
#include "test_unit/create_ok.hpp"
|
||||
#include "test_unit/create_ok_frame.hpp"
|
||||
#include "test_unit/create_query_frame.hpp"
|
||||
#include "test_unit/mock_execution_processor.hpp"
|
||||
#include "test_unit/printing.hpp"
|
||||
|
||||
@ -72,12 +76,11 @@ BOOST_AUTO_TEST_CASE(text_query)
|
||||
fix.proc.num_calls().reset(1).on_num_meta(1).on_meta(1).validate();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(prepared_statement)
|
||||
BOOST_AUTO_TEST_CASE(stmt_success)
|
||||
{
|
||||
// Setup
|
||||
auto stmt = statement_builder().id(1).num_params(2).build();
|
||||
const auto params = make_fv_arr("test", nullptr);
|
||||
fixture fix(any_execution_request(stmt, params));
|
||||
fixture fix(any_execution_request({std::uint32_t(1u), std::uint16_t(2u), params}));
|
||||
|
||||
// Run the algo
|
||||
algo_test()
|
||||
@ -100,18 +103,60 @@ BOOST_AUTO_TEST_CASE(prepared_statement)
|
||||
fix.proc.num_calls().reset(1).on_num_meta(1).on_meta(1).validate();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(error_num_params)
|
||||
BOOST_AUTO_TEST_CASE(stmt_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));
|
||||
fixture fix(any_execution_request({std::uint32_t(1u), std::uint16_t(2u), params}));
|
||||
|
||||
// Run the algo. Nothing should be written to the server
|
||||
algo_test().check(fix, client_errc::wrong_num_params);
|
||||
}
|
||||
|
||||
// We didn't modify the processor
|
||||
fix.proc.num_calls().validate();
|
||||
BOOST_AUTO_TEST_CASE(with_params_success)
|
||||
{
|
||||
// Setup
|
||||
const std::array<format_arg, 2> args{
|
||||
{{"", "abc"}, {"", 42}}
|
||||
};
|
||||
fixture fix(any_execution_request({"SELECT {}, {}", args}));
|
||||
fix.st.current_charset = utf8mb4_charset;
|
||||
|
||||
// Run the algo
|
||||
algo_test()
|
||||
.expect_write(create_query_frame(0, "SELECT 'abc', 42"))
|
||||
.expect_read(create_ok_frame(1, ok_builder().build()))
|
||||
.check(fix);
|
||||
|
||||
// Verify
|
||||
BOOST_TEST(fix.proc.encoding() == resultset_encoding::text);
|
||||
BOOST_TEST(fix.proc.sequence_number() == 2u);
|
||||
BOOST_TEST(fix.proc.is_complete());
|
||||
fix.proc.num_calls().reset(1).on_head_ok_packet(1).validate();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(with_params_error_unknown_charset)
|
||||
{
|
||||
// Setup
|
||||
const std::array<format_arg, 2> args{
|
||||
{{"", "abc"}, {"", 42}}
|
||||
};
|
||||
fixture fix(any_execution_request({"SELECT {}, {}", args}));
|
||||
fix.st.current_charset = {};
|
||||
|
||||
// The algo fails immediately
|
||||
algo_test().check(fix, client_errc::unknown_character_set);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(with_params_error_formatting)
|
||||
{
|
||||
// Setup
|
||||
const std::array<format_arg, 1> args{{{"", "abc"}}};
|
||||
fixture fix(any_execution_request({"SELECT {}, {}", args}));
|
||||
fix.st.current_charset = utf8mb4_charset;
|
||||
|
||||
// The algo fails immediately
|
||||
algo_test().check(fix, client_errc::format_arg_not_found);
|
||||
}
|
||||
|
||||
// This covers errors in both writing the request and calling read_resultset_head
|
||||
|
@ -5,86 +5,59 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <boost/mysql/connection.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "test_common/netfun_maker.hpp"
|
||||
#include "test_common/printing.hpp"
|
||||
#include "test_common/network_result.hpp"
|
||||
#include "test_unit/create_ok.hpp"
|
||||
#include "test_unit/create_ok_frame.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
|
||||
using namespace boost::mysql;
|
||||
using namespace boost::mysql::test;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(test_connection_use_after_move)
|
||||
|
||||
using test_connection = connection<test_stream>;
|
||||
|
||||
using query_netfun_maker = netfun_maker<void, test_connection, const string_view&, results&>;
|
||||
|
||||
struct
|
||||
{
|
||||
query_netfun_maker::signature query;
|
||||
const char* name;
|
||||
} all_fns[] = {
|
||||
{query_netfun_maker::sync_errc(&test_connection::execute), "sync" },
|
||||
{query_netfun_maker::async_diag(&test_connection::async_execute), "async"},
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(use_move_constructed_connection)
|
||||
{
|
||||
for (const auto& fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
// Construct connection
|
||||
test_connection conn;
|
||||
// Construct connection
|
||||
auto conn = create_test_any_connection();
|
||||
|
||||
// Use it
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
results result;
|
||||
fns.query(conn, "SELECT * FROM myt", result).validate_no_error();
|
||||
// Use it
|
||||
get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
results result;
|
||||
conn.async_execute("SELECT * FROM myt", result, as_netresult).validate_no_error();
|
||||
|
||||
// Move construct another connection from conn
|
||||
test_connection conn2(std::move(conn));
|
||||
// Move construct another connection from conn
|
||||
any_connection conn2(std::move(conn));
|
||||
|
||||
// Using it works (no dangling pointer)
|
||||
conn2.stream().add_bytes(create_ok_frame(1, ok_builder().affected_rows(42).build()));
|
||||
fns.query(conn2, "DELETE FROM myt", result).validate_no_error();
|
||||
BOOST_TEST(result.affected_rows() == 42u);
|
||||
}
|
||||
}
|
||||
// Using it works (no dangling pointer)
|
||||
get_stream(conn2).add_bytes(create_ok_frame(1, ok_builder().affected_rows(42).build()));
|
||||
conn2.async_execute("DELETE FROM myt", result, as_netresult).validate_no_error();
|
||||
BOOST_TEST(result.affected_rows() == 42u);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(use_move_assigned_connection)
|
||||
{
|
||||
for (const auto& fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
// Construct two connections
|
||||
test_connection conn1;
|
||||
test_connection conn2;
|
||||
// Construct two connections
|
||||
auto conn1 = create_test_any_connection();
|
||||
auto conn2 = create_test_any_connection();
|
||||
|
||||
// Use them
|
||||
conn1.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
conn2.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
results result;
|
||||
fns.query(conn1, "SELECT * FROM myt", result).validate_no_error();
|
||||
fns.query(conn2, "SELECT * FROM myt", result).validate_no_error();
|
||||
// Use them
|
||||
get_stream(conn1).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
get_stream(conn2).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
results result;
|
||||
conn1.async_execute("SELECT * FROM myt", result, as_netresult).validate_no_error();
|
||||
conn2.async_execute("SELECT * FROM myt", result, as_netresult).validate_no_error();
|
||||
|
||||
// Move assign
|
||||
conn2 = std::move(conn1);
|
||||
// Move assign
|
||||
conn2 = std::move(conn1);
|
||||
|
||||
// Using it works (no dangling pointer)
|
||||
conn2.stream().add_bytes(create_ok_frame(1, ok_builder().affected_rows(42).build()));
|
||||
fns.query(conn2, "DELETE FROM myt", result).validate_no_error();
|
||||
BOOST_TEST(result.affected_rows() == 42u);
|
||||
}
|
||||
}
|
||||
// Using it works (no dangling pointer)
|
||||
get_stream(conn2).add_bytes(create_ok_frame(1, ok_builder().affected_rows(42).build()));
|
||||
conn2.async_execute("DELETE FROM myt", result, as_netresult).validate_no_error();
|
||||
BOOST_TEST(result.affected_rows() == 42u);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
628
test/unit/test/spotchecks/execution_requests.cpp
Normal file
628
test/unit/test/spotchecks/execution_requests.cpp
Normal file
@ -0,0 +1,628 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Since integration tests can't reliably test multifunction operations
|
||||
// that span over multiple messages, we test the complete multifn fllow in this unit tests.
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/character_set.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/tcp.hpp>
|
||||
#include <boost/mysql/with_params.hpp>
|
||||
|
||||
#include <boost/mysql/detail/access.hpp>
|
||||
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/test/unit_test_suite.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test_common/assert_buffer_equals.hpp"
|
||||
#include "test_common/network_result.hpp"
|
||||
#include "test_common/tracker_executor.hpp"
|
||||
#include "test_unit/create_ok.hpp"
|
||||
#include "test_unit/create_ok_frame.hpp"
|
||||
#include "test_unit/create_query_frame.hpp"
|
||||
#include "test_unit/create_statement.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
using namespace boost::mysql;
|
||||
using namespace boost::mysql::test;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(test_execution_requests)
|
||||
|
||||
// The execution request is forwarded correctly, and non-const objects work correctly.
|
||||
// This is relevant for with_params involving ranges::views::filter, for instance
|
||||
// Note: netmakers intentionally not used here
|
||||
BOOST_AUTO_TEST_SUITE(forwarding_constness)
|
||||
|
||||
struct fixture
|
||||
{
|
||||
any_connection conn{create_test_any_connection()};
|
||||
results result;
|
||||
execution_state st;
|
||||
|
||||
fixture() { get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build())); }
|
||||
|
||||
void validate_bytes_written()
|
||||
{
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
|
||||
get_stream(conn).bytes_written(),
|
||||
create_query_frame(0, "SELECT 'abc'")
|
||||
);
|
||||
}
|
||||
|
||||
struct request
|
||||
{
|
||||
string_view value;
|
||||
operator string_view() { return value; } // Intentionally non-const
|
||||
};
|
||||
static request make_request() { return {"SELECT 'abc'"}; }
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Run
|
||||
conn.execute(make_request(), result, ec, diag);
|
||||
|
||||
// Validate
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_exc, fixture)
|
||||
{
|
||||
conn.execute(make_request(), result);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_execute(make_request(), result, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_execute(make_request(), result, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_deferred, fixture)
|
||||
{
|
||||
// Spotcheck: deferred works correctly
|
||||
auto op = conn.async_execute(make_request(), result, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Run
|
||||
conn.start_execution(make_request(), st, ec, diag);
|
||||
|
||||
// Validate
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_exc, fixture)
|
||||
{
|
||||
conn.start_execution(make_request(), st);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_start_execution(make_request(), st, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_start_execution(make_request(), st, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_deferred, fixture)
|
||||
{
|
||||
// Spotcheck: deferred works correctly
|
||||
auto op = conn.async_start_execution(make_request(), st, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
// Spotcheck: old connection doesn't generate compile errors.
|
||||
// Intentionally not run
|
||||
void old_connection()
|
||||
{
|
||||
tcp_connection conn(global_context_executor());
|
||||
auto req = fixture::make_request();
|
||||
results result;
|
||||
execution_state st;
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
conn.execute(req, result, ec, diag);
|
||||
conn.execute(req, result);
|
||||
conn.async_execute(req, result, asio::detached);
|
||||
conn.async_execute(req, result, diag, asio::detached);
|
||||
conn.async_execute(req, result, diag, asio::deferred)(asio::detached);
|
||||
conn.start_execution(req, st, ec, diag);
|
||||
conn.start_execution(req, st);
|
||||
conn.async_start_execution(req, st, asio::detached);
|
||||
conn.async_start_execution(req, st, diag, asio::detached);
|
||||
conn.async_start_execution(req, st, diag, asio::deferred)(asio::detached);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
// The execution request is forwarded with the correct value category, and is moved if required.
|
||||
// Move-only objects work correctly
|
||||
BOOST_AUTO_TEST_SUITE(forwarding_rvalues)
|
||||
|
||||
struct fixture
|
||||
{
|
||||
any_connection conn{create_test_any_connection()};
|
||||
results result;
|
||||
execution_state st;
|
||||
|
||||
fixture() { get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build())); }
|
||||
|
||||
void validate_bytes_written()
|
||||
{
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
|
||||
get_stream(conn).bytes_written(),
|
||||
create_query_frame(0, "SELECT 'abcd'")
|
||||
);
|
||||
}
|
||||
|
||||
// An execution request that doesn't support copies
|
||||
struct request
|
||||
{
|
||||
std::unique_ptr<char[]> buff;
|
||||
std::size_t size;
|
||||
|
||||
request(string_view v) : buff(new char[v.size()]), size(v.size())
|
||||
{
|
||||
std::copy(v.begin(), v.end(), buff.get());
|
||||
}
|
||||
|
||||
operator string_view() const { return {buff.get(), size}; }
|
||||
};
|
||||
static request make_request() { return {"SELECT 'abcd'"}; }
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Execute
|
||||
conn.execute(make_request(), result, ec, diag);
|
||||
|
||||
// Check
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_exc, fixture)
|
||||
{
|
||||
conn.execute(make_request(), result);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_execute(make_request(), result, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_execute(make_request(), result, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_deferred, fixture)
|
||||
{
|
||||
auto op = conn.async_execute(make_request(), result, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Execute
|
||||
conn.start_execution(make_request(), st, ec, diag);
|
||||
|
||||
// Check
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_exc, fixture)
|
||||
{
|
||||
conn.start_execution(make_request(), st);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_start_execution(make_request(), st, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_start_execution(make_request(), st, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_deferred, fixture)
|
||||
{
|
||||
auto op = conn.async_start_execution(make_request(), st, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
// Spotcheck: old connection doesn't generate compile errors.
|
||||
// Intentionally not run
|
||||
void old_connection()
|
||||
{
|
||||
tcp_connection conn(global_context_executor());
|
||||
results result;
|
||||
execution_state st;
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
conn.execute(fixture::make_request(), result, ec, diag);
|
||||
conn.execute(fixture::make_request(), result);
|
||||
conn.async_execute(fixture::make_request(), result, asio::detached);
|
||||
conn.async_execute(fixture::make_request(), result, diag, asio::detached);
|
||||
conn.async_execute(fixture::make_request(), result, diag, asio::deferred)(asio::detached);
|
||||
conn.start_execution(fixture::make_request(), st, ec, diag);
|
||||
conn.start_execution(fixture::make_request(), st);
|
||||
conn.async_start_execution(fixture::make_request(), st, asio::detached);
|
||||
conn.async_start_execution(fixture::make_request(), st, diag, asio::detached);
|
||||
conn.async_start_execution(fixture::make_request(), st, diag, asio::deferred)(asio::detached);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
// The execution request is forwarded correctly, with the correct value category.
|
||||
// Lvalues are not moved
|
||||
BOOST_AUTO_TEST_SUITE(forwarding_lvalues)
|
||||
|
||||
struct fixture
|
||||
{
|
||||
// A request where we can detect moved-from state.
|
||||
// std::string move-constructor doesn't offer guarantees about moved-from objects,
|
||||
// while std::vector does
|
||||
struct vector_request
|
||||
{
|
||||
std::vector<char> buff;
|
||||
|
||||
vector_request(string_view v) : buff(v.begin(), v.end()) {}
|
||||
|
||||
operator string_view() const { return {buff.data(), buff.size()}; }
|
||||
};
|
||||
|
||||
any_connection conn{create_test_any_connection()};
|
||||
results result;
|
||||
execution_state st;
|
||||
vector_request req{"SELECT 'abcd'"};
|
||||
|
||||
fixture() { get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build())); }
|
||||
|
||||
void validate_bytes_written()
|
||||
{
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
|
||||
get_stream(conn).bytes_written(),
|
||||
create_query_frame(0, "SELECT 'abcd'")
|
||||
);
|
||||
BOOST_TEST(static_cast<string_view>(req) == "SELECT 'abcd'");
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Execute
|
||||
conn.execute(req, result, ec, diag);
|
||||
|
||||
// Validate
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_sync_exc, fixture)
|
||||
{
|
||||
conn.execute(req, result);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_execute(req, result, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_execute(req, result, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(execute_async_deferred, fixture)
|
||||
{
|
||||
auto op = conn.async_execute(req, result, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_errc, fixture)
|
||||
{
|
||||
// Setup
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
|
||||
// Execute
|
||||
conn.start_execution(req, st, ec, diag);
|
||||
|
||||
// Check
|
||||
BOOST_TEST(ec == error_code());
|
||||
BOOST_TEST(diag == diagnostics());
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_sync_exc, fixture)
|
||||
{
|
||||
conn.start_execution(req, st);
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_diag, fixture)
|
||||
{
|
||||
diagnostics diag;
|
||||
conn.async_start_execution(req, st, diag, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_nodiag, fixture)
|
||||
{
|
||||
conn.async_start_execution(req, st, as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(start_execution_async_deferred, fixture)
|
||||
{
|
||||
auto op = conn.async_start_execution(req, st, asio::deferred);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
validate_bytes_written();
|
||||
}
|
||||
|
||||
// Spotcheck: old connection doesn't generate compile errors.
|
||||
// Intentionally not run
|
||||
void old_connection()
|
||||
{
|
||||
tcp_connection conn(global_context_executor());
|
||||
results result;
|
||||
execution_state st;
|
||||
error_code ec;
|
||||
diagnostics diag;
|
||||
const fixture::vector_request req{"SELECT 'abc'"};
|
||||
|
||||
conn.execute(req, result, ec, diag);
|
||||
conn.execute(req, result);
|
||||
conn.async_execute(req, result, asio::detached);
|
||||
conn.async_execute(req, result, diag, asio::detached);
|
||||
conn.async_execute(req, result, diag, asio::deferred)(asio::detached);
|
||||
conn.start_execution(req, st, ec, diag);
|
||||
conn.start_execution(req, st);
|
||||
conn.async_start_execution(req, st, asio::detached);
|
||||
conn.async_start_execution(req, st, diag, asio::detached);
|
||||
conn.async_start_execution(req, st, diag, asio::deferred)(asio::detached);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
// Deferred tokens appropriately decay-copy lvalues
|
||||
BOOST_AUTO_TEST_SUITE(deferred_tokens_lvalues)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(execute)
|
||||
{
|
||||
// Setup
|
||||
auto conn = create_test_any_connection();
|
||||
results result;
|
||||
get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
std::string req(128, 'a');
|
||||
|
||||
// Create a deferred op
|
||||
auto op = conn.async_execute(req, result, asio::deferred);
|
||||
|
||||
// Mutate the argument
|
||||
std::fill(req.begin(), req.end(), 'b');
|
||||
|
||||
// Initiate
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// We wrote the initial value
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
|
||||
get_stream(conn).bytes_written(),
|
||||
create_query_frame(0, std::string(128, 'a'))
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(start_execution)
|
||||
{
|
||||
// Setup
|
||||
auto conn = create_test_any_connection();
|
||||
execution_state st;
|
||||
get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
std::string req(128, 'a');
|
||||
|
||||
// Create a deferred op
|
||||
auto op = conn.async_start_execution(req, st, asio::deferred);
|
||||
|
||||
// Mutate the argument
|
||||
std::fill(req.begin(), req.end(), 'b');
|
||||
|
||||
// Initiate
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// We wrote the initial value
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
|
||||
get_stream(conn).bytes_written(),
|
||||
create_query_frame(0, std::string(128, 'a'))
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
// Spotcheck: the types returned by with_params are correct
|
||||
BOOST_AUTO_TEST_CASE(with_params_types)
|
||||
{
|
||||
{
|
||||
// const references
|
||||
const std::string s = "abc";
|
||||
auto p1 = with_params("SELECT {}", s);
|
||||
static_assert(std::is_same<decltype(p1), with_params_t<std::string>>::value, "");
|
||||
}
|
||||
{
|
||||
// references
|
||||
std::string s = "abc";
|
||||
auto p = with_params("SELECT {}", s);
|
||||
static_assert(std::is_same<decltype(p), with_params_t<std::string>>::value, "");
|
||||
}
|
||||
{
|
||||
// rvalue references
|
||||
std::string s = "abc";
|
||||
auto p = with_params("SELECT {}", std::move(s));
|
||||
static_assert(std::is_same<decltype(p), with_params_t<std::string>>::value, "");
|
||||
}
|
||||
{
|
||||
// pure rvalues
|
||||
auto p = with_params("SELECT {}", std::string());
|
||||
static_assert(std::is_same<decltype(p), with_params_t<std::string>>::value, "");
|
||||
}
|
||||
{
|
||||
// std::ref
|
||||
std::string s = "abc";
|
||||
auto p = with_params("SELECT {}", std::ref(s));
|
||||
static_assert(std::is_same<decltype(p), with_params_t<std::string&>>::value, "");
|
||||
}
|
||||
{
|
||||
// std::ref (const)
|
||||
const std::string s = "abc";
|
||||
auto p = with_params("SELECT {}", std::ref(s));
|
||||
static_assert(std::is_same<decltype(p), with_params_t<const std::string&>>::value, "");
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test: async_execute() doesn't cause side effects in the initiation
|
||||
BOOST_AUTO_TEST_CASE(async_execute_side_effects_in_initiation)
|
||||
{
|
||||
auto conn = create_test_any_connection();
|
||||
results result1, result2;
|
||||
|
||||
// Resultsets will be complete as soon as a message is read
|
||||
get_stream(conn)
|
||||
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(2).build()))
|
||||
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(1).build()));
|
||||
|
||||
// Create two queries as deferred objects, but don't run them yet
|
||||
auto q1 = conn.async_execute("Q1", result1, asio::deferred);
|
||||
auto q2 = conn.async_execute("Q2", result2, asio::deferred);
|
||||
|
||||
// Run them in reverse order
|
||||
std::move(q2)(as_netresult).validate_no_error();
|
||||
std::move(q1)(as_netresult).validate_no_error();
|
||||
|
||||
// Check that we wrote Q2's message first, then Q1's
|
||||
auto expected = concat_copy(
|
||||
create_frame(0, {0x03, 0x51, 0x32}), // query request Q2
|
||||
create_frame(0, {0x03, 0x51, 0x31}) // query request Q1
|
||||
);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(get_stream(conn).bytes_written(), expected);
|
||||
|
||||
// Check that the results got the right ok_packets
|
||||
BOOST_TEST(result2.affected_rows() == 2u);
|
||||
BOOST_TEST(result1.affected_rows() == 1u);
|
||||
}
|
||||
|
||||
// Regression test: bound statements correctly store statement handle and params
|
||||
// when used with deferred tokens
|
||||
BOOST_AUTO_TEST_CASE(async_execute_deferred_lifetimes)
|
||||
{
|
||||
// Setup
|
||||
constexpr std::uint8_t expected_msg[] = {
|
||||
0x15, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x02, 0x01, 0xfe, 0x00, 0x06, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74,
|
||||
};
|
||||
results result;
|
||||
auto conn = create_test_any_connection();
|
||||
get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
|
||||
// Create a bound statement on the heap. This helps tooling detect memory errors
|
||||
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
|
||||
std::unique_ptr<bound_stmt_t> stmt_ptr{
|
||||
new bound_stmt_t{statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr)}
|
||||
};
|
||||
|
||||
// Deferred op
|
||||
auto op = conn.async_execute(*stmt_ptr, result, asio::deferred);
|
||||
|
||||
// Free the statement
|
||||
stmt_ptr.reset();
|
||||
|
||||
// Actually run the op
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// verify that the op had the intended effects
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(get_stream(conn).bytes_written(), expected_msg);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/blob.hpp>
|
||||
#include <boost/mysql/column_type.hpp>
|
||||
#include <boost/mysql/connection.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
@ -42,6 +43,7 @@
|
||||
#include "test_unit/create_row_message.hpp"
|
||||
#include "test_unit/create_statement.hpp"
|
||||
#include "test_unit/fail_count.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
using namespace boost::mysql::test;
|
||||
@ -50,51 +52,17 @@ namespace asio = boost::asio;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(test_misc)
|
||||
|
||||
using test_connection = connection<test_stream>;
|
||||
|
||||
// Make sure async_execute() and friends don't cause side
|
||||
// effects in the initiation
|
||||
BOOST_AUTO_TEST_CASE(async_execute_side_effects_in_initiation)
|
||||
{
|
||||
test_connection conn;
|
||||
results result1, result2;
|
||||
|
||||
// Resultsets will be complete as soon as a message is read
|
||||
conn.stream()
|
||||
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(2).build()))
|
||||
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(1).build()));
|
||||
|
||||
// Create two queries as deferred objects, but don't run them yet
|
||||
auto q1 = conn.async_execute("Q1", result1, asio::deferred);
|
||||
auto q2 = conn.async_execute("Q2", result2, asio::deferred);
|
||||
|
||||
// Run them in reverse order
|
||||
std::move(q2)(as_netresult).validate_no_error();
|
||||
std::move(q1)(as_netresult).validate_no_error();
|
||||
|
||||
// Check that we wrote Q2's message first, then Q1's
|
||||
auto expected = concat_copy(
|
||||
create_frame(0, {0x03, 0x51, 0x32}), // query request Q2
|
||||
create_frame(0, {0x03, 0x51, 0x31}) // query request Q1
|
||||
);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected);
|
||||
|
||||
// Check that the results got the right ok_packets
|
||||
BOOST_TEST(result2.affected_rows() == 2u);
|
||||
BOOST_TEST(result1.affected_rows() == 1u);
|
||||
}
|
||||
|
||||
// spotcheck for the dynamic interface
|
||||
// Verifies that execute (dynamic interface) works when rows come in separate batches
|
||||
// This is testing the interaction between the network algorithm and results
|
||||
BOOST_AUTO_TEST_CASE(execute_multiple_batches)
|
||||
{
|
||||
// Setup
|
||||
test_connection conn;
|
||||
auto conn = create_test_any_connection();
|
||||
results result;
|
||||
|
||||
// Message sequence (each on its own read)
|
||||
conn.stream()
|
||||
get_stream(conn)
|
||||
.add_bytes(create_frame(1, {0x02})) // OK, 2 columns
|
||||
.add_break()
|
||||
.add_bytes(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef())) // meta
|
||||
@ -126,7 +94,7 @@ BOOST_AUTO_TEST_CASE(execute_multiple_batches)
|
||||
|
||||
// We've written the query request
|
||||
auto expected_msg = create_frame(0, {0x03, 0x61, 0x62, 0x63}); // ASCII "abc" (plus length)
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected_msg);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(get_stream(conn).bytes_written(), expected_msg);
|
||||
|
||||
// We've populated the results
|
||||
BOOST_TEST_REQUIRE(result.size() == 3u);
|
||||
@ -141,150 +109,20 @@ BOOST_AUTO_TEST_CASE(execute_multiple_batches)
|
||||
BOOST_TEST(result[2].rows() == makerows(1, "ab"));
|
||||
}
|
||||
|
||||
// Regression check: execute statement with iterator range with a reference type that is convertible to
|
||||
// field_view, but not equal to field_view
|
||||
BOOST_AUTO_TEST_CASE(stmt_iterator_reference_not_field_view)
|
||||
{
|
||||
results result;
|
||||
auto stmt = statement_builder().id(1).num_params(2).build();
|
||||
test_connection conn;
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().affected_rows(50).info("1st").build()));
|
||||
|
||||
// Call the function
|
||||
std::vector<field> fields{field_view("test"), field_view()};
|
||||
conn.execute(stmt.bind(fields.begin(), fields.end()), result);
|
||||
|
||||
// Verify the message we sent
|
||||
constexpr std::uint8_t expected_msg[] = {
|
||||
0x15, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x02, 0x01, 0xfe, 0x00, 0x06, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74,
|
||||
};
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected_msg);
|
||||
|
||||
// Verify the results
|
||||
BOOST_TEST_REQUIRE(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().size() == 0u);
|
||||
BOOST_TEST(result.affected_rows() == 50u);
|
||||
BOOST_TEST(result.info() == "1st");
|
||||
}
|
||||
|
||||
// The serialized form of executing a statement with ID=1, params=("test", nullptr)
|
||||
constexpr std::uint8_t execute_stmt_msg[] = {
|
||||
0x15, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x02, 0x01, 0xfe, 0x00, 0x06, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74,
|
||||
};
|
||||
|
||||
// Verify that we correctly perform a decay-copy of the execution request
|
||||
// in async_execute(), relevant for deferred tokens
|
||||
BOOST_AUTO_TEST_CASE(async_execute_deferred_lifetimes_rvalues)
|
||||
{
|
||||
test_connection conn;
|
||||
|
||||
results result;
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
|
||||
|
||||
// Deferred op. Execution request is a temporary
|
||||
auto op = conn.async_execute(
|
||||
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
|
||||
result,
|
||||
asio::deferred
|
||||
);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// verify that the op had the intended effects
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
|
||||
BOOST_TEST(result.info() == "1st");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(async_execute_deferred_lifetimes_lvalues)
|
||||
{
|
||||
test_connection conn;
|
||||
|
||||
results result;
|
||||
|
||||
// Create a bound statement on the heap. This helps tooling detect memory errors
|
||||
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
|
||||
auto stmt = statement_builder().id(1).num_params(2).build();
|
||||
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
|
||||
|
||||
// Messages
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
|
||||
|
||||
// Deferred op
|
||||
auto op = conn.async_execute(*stmt_ptr, result, asio::deferred);
|
||||
|
||||
// Free the statement
|
||||
stmt_ptr.reset();
|
||||
|
||||
// Actually run the op
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// verify that the op had the intended effects
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
|
||||
BOOST_TEST(result.info() == "1st");
|
||||
}
|
||||
|
||||
// Verify that we correctly perform a decay-copy of the parameters and the
|
||||
// statement handle for async_start_execution(), relevant for deferred tokens
|
||||
BOOST_AUTO_TEST_CASE(async_start_execution_deferred_lifetimes_rvalues)
|
||||
{
|
||||
test_connection conn;
|
||||
|
||||
execution_state st;
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
|
||||
|
||||
// Deferred op. Execution request is a temporary
|
||||
auto op = conn.async_start_execution(
|
||||
statement_builder().id(1).num_params(2).build().bind(std::string("test"), nullptr),
|
||||
st,
|
||||
asio::deferred
|
||||
);
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// verify that the op had the intended effects
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(deferred_lifetimes_statement)
|
||||
{
|
||||
test_connection conn;
|
||||
execution_state st;
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().info("1st").build()));
|
||||
|
||||
// Create a bound statement on the heap. This helps tooling detect memory errors
|
||||
using bound_stmt_t = bound_statement_tuple<std::tuple<std::string, std::nullptr_t>>;
|
||||
auto stmt = statement_builder().id(1).num_params(2).build();
|
||||
std::unique_ptr<bound_stmt_t> stmt_ptr{new bound_stmt_t{stmt.bind(std::string("test"), nullptr)}};
|
||||
|
||||
// Deferred op
|
||||
auto op = conn.async_start_execution(*stmt_ptr, st, asio::deferred);
|
||||
|
||||
// Free the statement
|
||||
stmt_ptr.reset();
|
||||
|
||||
// Actually run the op
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
|
||||
// verify that the op had the intended effects
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), execute_stmt_msg);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
}
|
||||
|
||||
// Verify that async_close_statement doesn't require the passed-in statement to be alive. Only
|
||||
// relevant for deferred tokens.
|
||||
// Regression check: async_close_statement doesn't require the passed-in statement to be alive
|
||||
// when used with deferred tokens.
|
||||
BOOST_AUTO_TEST_CASE(async_close_statement_handle_deferred_tokens)
|
||||
{
|
||||
test_connection conn;
|
||||
|
||||
auto stmt = statement_builder().id(3).build();
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
// Setup
|
||||
auto conn = create_test_any_connection();
|
||||
std::unique_ptr<statement> stmt{new statement(statement_builder().id(3).build())};
|
||||
get_stream(conn).add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
|
||||
// Deferred op
|
||||
auto op = conn.async_close_statement(stmt, asio::deferred);
|
||||
auto op = conn.async_close_statement(*stmt, asio::deferred);
|
||||
|
||||
// Invalidate the original variable
|
||||
stmt = statement_builder().id(42).build();
|
||||
stmt.reset();
|
||||
|
||||
// Run the operation
|
||||
std::move(op)(as_netresult).validate_no_error();
|
||||
@ -294,21 +132,21 @@ BOOST_AUTO_TEST_CASE(async_close_statement_handle_deferred_tokens)
|
||||
create_frame(0, {0x19, 0x03, 0x00, 0x00, 0x00}),
|
||||
create_frame(0, {0x0e})
|
||||
);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(conn.stream().bytes_written(), expected_message);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(get_stream(conn).bytes_written(), expected_message);
|
||||
}
|
||||
|
||||
// Regression check: when there is a network error, sync functions
|
||||
// returning a value fail with an assertion
|
||||
BOOST_AUTO_TEST_CASE(net_error_prepare_statement)
|
||||
{
|
||||
using netmaker_stmt = netfun_maker<statement, test_connection, string_view>;
|
||||
using netmaker_stmt = netfun_maker<statement, any_connection, string_view>;
|
||||
struct
|
||||
{
|
||||
const char* name;
|
||||
netmaker_stmt::signature prepare_statement;
|
||||
} fns[] = {
|
||||
{"sync", netmaker_stmt::sync_errc(&test_connection::prepare_statement) },
|
||||
{"async", netmaker_stmt::async_diag(&test_connection::async_prepare_statement)},
|
||||
{"sync", netmaker_stmt::sync_errc(&any_connection::prepare_statement) },
|
||||
{"async", netmaker_stmt::async_diag(&any_connection::async_prepare_statement)},
|
||||
};
|
||||
|
||||
for (const auto& fn : fns)
|
||||
@ -316,8 +154,8 @@ BOOST_AUTO_TEST_CASE(net_error_prepare_statement)
|
||||
BOOST_TEST_CONTEXT(fn.name)
|
||||
{
|
||||
// Setup
|
||||
test_connection conn;
|
||||
conn.stream().set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
auto conn = create_test_any_connection();
|
||||
get_stream(conn).set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
|
||||
fn.prepare_statement(conn, "SELECT 1").validate_error(boost::asio::error::connection_reset);
|
||||
}
|
||||
@ -326,14 +164,14 @@ BOOST_AUTO_TEST_CASE(net_error_prepare_statement)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(net_error_read_some_rows)
|
||||
{
|
||||
using netmaker_stmt = netfun_maker<rows_view, test_connection, execution_state&>;
|
||||
using netmaker_stmt = netfun_maker<rows_view, any_connection, execution_state&>;
|
||||
struct
|
||||
{
|
||||
const char* name;
|
||||
netmaker_stmt::signature read_some_rows;
|
||||
} fns[] = {
|
||||
{"sync", netmaker_stmt::sync_errc(&test_connection::read_some_rows) },
|
||||
{"async", netmaker_stmt::async_diag(&test_connection::async_read_some_rows)},
|
||||
{"sync", netmaker_stmt::sync_errc(&any_connection::read_some_rows) },
|
||||
{"async", netmaker_stmt::async_diag(&any_connection::async_read_some_rows)},
|
||||
};
|
||||
|
||||
for (const auto& fn : fns)
|
||||
@ -341,8 +179,8 @@ BOOST_AUTO_TEST_CASE(net_error_read_some_rows)
|
||||
BOOST_TEST_CONTEXT(fn.name)
|
||||
{
|
||||
// Setup
|
||||
test_connection conn;
|
||||
conn.stream().set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
auto conn = create_test_any_connection();
|
||||
get_stream(conn).set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
execution_state st;
|
||||
add_meta(get_iface(st), {column_type::bigint});
|
||||
|
||||
@ -353,14 +191,14 @@ BOOST_AUTO_TEST_CASE(net_error_read_some_rows)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(net_error_void_signature)
|
||||
{
|
||||
using netmaker_execute = netfun_maker<void, test_connection, const string_view&, results&>;
|
||||
using netmaker_execute = netfun_maker<void, any_connection, const string_view&, results&>;
|
||||
struct
|
||||
{
|
||||
const char* name;
|
||||
netmaker_execute::signature execute;
|
||||
} fns[] = {
|
||||
{"sync", netmaker_execute::sync_errc(&test_connection::execute) },
|
||||
{"async", netmaker_execute::async_diag(&test_connection::async_execute)},
|
||||
{"sync", netmaker_execute::sync_errc(&any_connection::execute) },
|
||||
{"async", netmaker_execute::async_diag(&any_connection::async_execute)},
|
||||
};
|
||||
|
||||
for (const auto& fn : fns)
|
||||
@ -368,8 +206,8 @@ BOOST_AUTO_TEST_CASE(net_error_void_signature)
|
||||
BOOST_TEST_CONTEXT(fn.name)
|
||||
{
|
||||
// Setup
|
||||
test_connection conn;
|
||||
conn.stream().set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
auto conn = create_test_any_connection();
|
||||
get_stream(conn).set_fail_count(fail_count(0, boost::asio::error::connection_reset));
|
||||
results r;
|
||||
|
||||
fn.execute(conn, "SELECT 1", r).validate_error(boost::asio::error::connection_reset);
|
||||
@ -377,74 +215,35 @@ BOOST_AUTO_TEST_CASE(net_error_void_signature)
|
||||
}
|
||||
}
|
||||
|
||||
// We can bind and execute statements using references
|
||||
BOOST_AUTO_TEST_CASE(stmt_tuple_ref)
|
||||
{
|
||||
// Setup
|
||||
std::string s = "abcdef";
|
||||
blob b{0x00, 0x01, 0x02};
|
||||
results r;
|
||||
auto stmt = statement_builder().id(1).num_params(2).build();
|
||||
|
||||
// Connection
|
||||
test_connection conn;
|
||||
conn.stream().add_bytes(create_ok_frame(1, ok_builder().build()));
|
||||
|
||||
// This should compile and run
|
||||
BOOST_CHECK_NO_THROW(conn.execute(stmt.bind(std::ref(s), std::ref(b)), r));
|
||||
}
|
||||
|
||||
// Helper to create any_connection objects using test_stream
|
||||
struct test_any_connection_fixture
|
||||
{
|
||||
any_connection conn;
|
||||
|
||||
test_any_connection_fixture()
|
||||
: conn(detail::access::construct<any_connection>(
|
||||
default_initial_read_buffer_size,
|
||||
static_cast<std::size_t>(-1), // no buffer limit
|
||||
std::unique_ptr<detail::engine>(
|
||||
new detail::engine_impl<detail::engine_stream_adaptor<test_stream>>()
|
||||
)
|
||||
))
|
||||
{
|
||||
}
|
||||
|
||||
test_stream& stream()
|
||||
{
|
||||
return detail::stream_from_engine<test_stream>(detail::access::get_impl(conn).get_engine());
|
||||
}
|
||||
};
|
||||
|
||||
auto pipeline_fn = netfun_maker<void, any_connection, const pipeline_request&, std::vector<stage_response>&>::
|
||||
async_diag(&any_connection::async_run_pipeline);
|
||||
|
||||
// empty pipelines complete immediately, posting adequately
|
||||
BOOST_FIXTURE_TEST_CASE(empty_pipeline, test_any_connection_fixture)
|
||||
BOOST_AUTO_TEST_CASE(empty_pipeline)
|
||||
{
|
||||
// Setup
|
||||
auto conn = create_test_any_connection();
|
||||
pipeline_request req;
|
||||
std::vector<stage_response> res;
|
||||
|
||||
// Run it. It should complete immediately, posting to the correct executor (verified by the testing
|
||||
// infrastructure)
|
||||
pipeline_fn(conn, req, res).validate_no_error();
|
||||
conn.async_run_pipeline(req, res, as_netresult).validate_no_error();
|
||||
BOOST_TEST(res.size() == 0u);
|
||||
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(get_stream(conn).bytes_written(), blob{});
|
||||
}
|
||||
|
||||
// fatal errors in pipelines behave correctly
|
||||
BOOST_FIXTURE_TEST_CASE(pipeline_fatal_error, test_any_connection_fixture)
|
||||
BOOST_AUTO_TEST_CASE(pipeline_fatal_error)
|
||||
{
|
||||
// Setup
|
||||
auto conn = create_test_any_connection();
|
||||
pipeline_request req;
|
||||
std::vector<stage_response> res;
|
||||
req.add_execute("SELECT 1").add_execute("SELECT 2");
|
||||
|
||||
// The first read will fail
|
||||
stream().set_fail_count(fail_count(1, boost::asio::error::network_reset));
|
||||
get_stream(conn).set_fail_count(fail_count(1, boost::asio::error::network_reset));
|
||||
|
||||
// Run it
|
||||
pipeline_fn(conn, req, res).validate_error(boost::asio::error::network_reset);
|
||||
conn.async_run_pipeline(req, res, as_netresult).validate_error(boost::asio::error::network_reset);
|
||||
|
||||
// Validate the results
|
||||
BOOST_TEST(res.size() == 2u);
|
||||
|
@ -8,21 +8,22 @@
|
||||
// Since integration tests can't reliably test multifunction operations
|
||||
// that span over multiple messages, we test the complete multifn fllow in this unit tests.
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/column_type.hpp>
|
||||
#include <boost/mysql/connection.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "test_common/check_meta.hpp"
|
||||
#include "test_common/netfun_maker.hpp"
|
||||
#include "test_common/network_result.hpp"
|
||||
#include "test_unit/create_coldef_frame.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_row_message.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
using namespace boost::mysql;
|
||||
@ -30,188 +31,143 @@ using namespace boost::mysql::test;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(test_multifn)
|
||||
|
||||
using test_connection = connection<test_stream>;
|
||||
|
||||
using start_query_netm = netfun_maker<void, test_connection, const string_view&, execution_state&>;
|
||||
using read_resultset_head_netm = netfun_maker<void, test_connection, execution_state&>;
|
||||
using read_some_rows_netm = netfun_maker<rows_view, test_connection, execution_state&>;
|
||||
|
||||
struct
|
||||
{
|
||||
start_query_netm::signature start_execution;
|
||||
read_resultset_head_netm::signature read_resultset_head;
|
||||
read_some_rows_netm::signature read_some_rows;
|
||||
const char* name;
|
||||
} all_fns[] = {
|
||||
{start_query_netm::sync_errc(&test_connection::start_execution),
|
||||
read_resultset_head_netm::sync_errc(&test_connection::read_resultset_head),
|
||||
read_some_rows_netm::sync_errc(&test_connection::read_some_rows),
|
||||
"sync" },
|
||||
{start_query_netm::async_diag(&test_connection::async_start_execution),
|
||||
read_resultset_head_netm::async_diag(&test_connection::async_read_resultset_head),
|
||||
read_some_rows_netm::async_diag(&test_connection::async_read_some_rows),
|
||||
"async"},
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(separate_batches)
|
||||
{
|
||||
for (auto fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
execution_state st;
|
||||
test_connection conn;
|
||||
conn.stream()
|
||||
.add_bytes(create_frame(1, {0x01}))
|
||||
.add_break()
|
||||
.add_bytes(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef()))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(3, "abc"))
|
||||
.add_break()
|
||||
.add_bytes(create_eof_frame(
|
||||
4,
|
||||
ok_builder().affected_rows(10u).info("1st").more_results(true).build()
|
||||
))
|
||||
.add_break()
|
||||
.add_bytes(create_frame(5, {0x01}))
|
||||
.add_break()
|
||||
.add_bytes(create_coldef_frame(6, meta_builder().type(column_type::decimal).build_coldef()))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(7, "ab"))
|
||||
.add_bytes(create_text_row_message(8, "plo"))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(9, "hju"))
|
||||
.add_bytes(create_eof_frame(10, ok_builder().affected_rows(30u).info("2nd").build()));
|
||||
execution_state st;
|
||||
auto conn = create_test_any_connection();
|
||||
get_stream(conn)
|
||||
.add_bytes(create_frame(1, {0x01}))
|
||||
.add_break()
|
||||
.add_bytes(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef()))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(3, "abc"))
|
||||
.add_break()
|
||||
.add_bytes(create_eof_frame(4, ok_builder().affected_rows(10u).info("1st").more_results(true).build())
|
||||
)
|
||||
.add_break()
|
||||
.add_bytes(create_frame(5, {0x01}))
|
||||
.add_break()
|
||||
.add_bytes(create_coldef_frame(6, meta_builder().type(column_type::decimal).build_coldef()))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(7, "ab"))
|
||||
.add_bytes(create_text_row_message(8, "plo"))
|
||||
.add_break()
|
||||
.add_bytes(create_text_row_message(9, "hju"))
|
||||
.add_bytes(create_eof_frame(10, ok_builder().affected_rows(30u).info("2nd").build()));
|
||||
|
||||
// Start
|
||||
fns.start_execution(conn, "SELECT 1", st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::varchar});
|
||||
// Start
|
||||
conn.async_start_execution("SELECT 1", st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::varchar});
|
||||
|
||||
// 1st resultset, row
|
||||
auto rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
BOOST_TEST(rv == makerows(1, "abc"));
|
||||
// 1st resultset, row
|
||||
auto rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
BOOST_TEST(rv == makerows(1, "abc"));
|
||||
|
||||
// 1st resultset, eof
|
||||
rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(rv == makerows(1));
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
// 1st resultset, eof
|
||||
rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(rv == makerows(1));
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
|
||||
// 2nd resultset, head
|
||||
fns.read_resultset_head(conn, st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::decimal});
|
||||
// 2nd resultset, head
|
||||
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::decimal});
|
||||
|
||||
// 2nd resultset, row batch
|
||||
rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
BOOST_TEST(rv == makerows(1, "ab", "plo"));
|
||||
// 2nd resultset, row batch
|
||||
rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
BOOST_TEST(rv == makerows(1, "ab", "plo"));
|
||||
|
||||
// 2nd resultset, last row & eof
|
||||
rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(rv == makerows(1, "hju"));
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
}
|
||||
}
|
||||
// 2nd resultset, last row & eof
|
||||
rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(rv == makerows(1, "hju"));
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
}
|
||||
|
||||
// The server sent us a single, big message with everything
|
||||
BOOST_AUTO_TEST_CASE(single_read)
|
||||
{
|
||||
for (auto fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
execution_state st;
|
||||
test_connection conn(buffer_params(4096));
|
||||
execution_state st;
|
||||
any_connection_params params;
|
||||
params.initial_buffer_size = 4096;
|
||||
auto conn = create_test_any_connection(params);
|
||||
|
||||
conn.stream()
|
||||
.add_bytes(create_frame(1, {0x01}))
|
||||
.add_bytes(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef()))
|
||||
.add_bytes(create_text_row_message(3, "abc"))
|
||||
.add_bytes(create_eof_frame(
|
||||
4,
|
||||
ok_builder().affected_rows(10u).info("1st").more_results(true).build()
|
||||
))
|
||||
.add_bytes(create_frame(5, {0x01}))
|
||||
.add_bytes(create_coldef_frame(6, meta_builder().type(column_type::decimal).build_coldef()))
|
||||
.add_bytes(create_text_row_message(7, "ab"))
|
||||
.add_bytes(create_text_row_message(8, "plo"))
|
||||
.add_bytes(create_text_row_message(9, "hju"))
|
||||
.add_bytes(create_eof_frame(10, ok_builder().affected_rows(30u).info("2nd").build()));
|
||||
get_stream(conn)
|
||||
.add_bytes(create_frame(1, {0x01}))
|
||||
.add_bytes(create_coldef_frame(2, meta_builder().type(column_type::varchar).build_coldef()))
|
||||
.add_bytes(create_text_row_message(3, "abc"))
|
||||
.add_bytes(create_eof_frame(4, ok_builder().affected_rows(10u).info("1st").more_results(true).build())
|
||||
)
|
||||
.add_bytes(create_frame(5, {0x01}))
|
||||
.add_bytes(create_coldef_frame(6, meta_builder().type(column_type::decimal).build_coldef()))
|
||||
.add_bytes(create_text_row_message(7, "ab"))
|
||||
.add_bytes(create_text_row_message(8, "plo"))
|
||||
.add_bytes(create_text_row_message(9, "hju"))
|
||||
.add_bytes(create_eof_frame(10, ok_builder().affected_rows(30u).info("2nd").build()));
|
||||
|
||||
// Start
|
||||
fns.start_execution(conn, "SELECT 1", st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::varchar});
|
||||
// Start
|
||||
conn.async_start_execution("SELECT 1", st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::varchar});
|
||||
|
||||
// First resultset
|
||||
auto rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(rv == makerows(1, "abc"));
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
// First resultset
|
||||
auto rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(rv == makerows(1, "abc"));
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
|
||||
// 2nd resultset, head
|
||||
fns.read_resultset_head(conn, st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::decimal});
|
||||
// 2nd resultset, head
|
||||
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
check_meta(st.meta(), {column_type::decimal});
|
||||
|
||||
// 2nd resultset
|
||||
rv = fns.read_some_rows(conn, st).get();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(rv == makerows(1, "ab", "plo", "hju"));
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
}
|
||||
}
|
||||
// 2nd resultset
|
||||
rv = conn.async_read_some_rows(st, as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(rv == makerows(1, "ab", "plo", "hju"));
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_resultsets)
|
||||
{
|
||||
for (auto fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
execution_state st;
|
||||
test_connection conn(buffer_params(4096));
|
||||
execution_state st;
|
||||
any_connection_params params;
|
||||
params.initial_buffer_size = 4096;
|
||||
auto conn = create_test_any_connection(params);
|
||||
|
||||
conn.stream()
|
||||
.add_bytes(
|
||||
create_ok_frame(1, ok_builder().affected_rows(10u).info("1st").more_results(true).build())
|
||||
)
|
||||
.add_bytes(
|
||||
create_ok_frame(2, ok_builder().affected_rows(20u).info("2nd").more_results(true).build())
|
||||
)
|
||||
.add_bytes(create_ok_frame(3, ok_builder().affected_rows(30u).info("3rd").build()));
|
||||
get_stream(conn)
|
||||
.add_bytes(create_ok_frame(1, ok_builder().affected_rows(10u).info("1st").more_results(true).build()))
|
||||
.add_bytes(create_ok_frame(2, ok_builder().affected_rows(20u).info("2nd").more_results(true).build()))
|
||||
.add_bytes(create_ok_frame(3, ok_builder().affected_rows(30u).info("3rd").build()));
|
||||
|
||||
// Start
|
||||
fns.start_execution(conn, "SELECT 1", st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
// Start
|
||||
conn.async_start_execution("SELECT 1", st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 10u);
|
||||
BOOST_TEST(st.info() == "1st");
|
||||
|
||||
// 2nd resultset
|
||||
fns.read_resultset_head(conn, st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 20u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
// 2nd resultset
|
||||
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.should_read_head());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 20u);
|
||||
BOOST_TEST(st.info() == "2nd");
|
||||
|
||||
// 3rd resultset
|
||||
fns.read_resultset_head(conn, st).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "3rd");
|
||||
}
|
||||
}
|
||||
// 3rd resultset
|
||||
conn.async_read_resultset_head(st, as_netresult).validate_no_error();
|
||||
BOOST_TEST_REQUIRE(st.complete());
|
||||
BOOST_TEST(st.meta().size() == 0u);
|
||||
BOOST_TEST(st.affected_rows() == 30u);
|
||||
BOOST_TEST(st.info() == "3rd");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -7,23 +7,22 @@
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
#include <boost/mysql/any_connection.hpp>
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/connection.hpp>
|
||||
#include <boost/mysql/static_execution_state.hpp>
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "test_common/netfun_maker.hpp"
|
||||
#include "test_common/network_result.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"
|
||||
#include "test_unit/create_row_message.hpp"
|
||||
#include "test_unit/test_any_connection.hpp"
|
||||
#include "test_unit/test_stream.hpp"
|
||||
|
||||
using namespace boost::mysql::test;
|
||||
using namespace boost::mysql;
|
||||
@ -31,37 +30,22 @@ using boost::span;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(test_read_some_rows_static)
|
||||
|
||||
using test_connection = connection<test_stream>;
|
||||
|
||||
using row1 = std::tuple<int, float>;
|
||||
using row2 = std::tuple<double>;
|
||||
|
||||
using state_t = static_execution_state<row1, row1, row2, row1, row2>;
|
||||
using netfun_maker_row1 = netfun_maker<std::size_t, test_connection, state_t&, span<row1> >;
|
||||
using netfun_maker_row2 = netfun_maker<std::size_t, test_connection, state_t&, span<row2> >;
|
||||
|
||||
struct
|
||||
{
|
||||
typename netfun_maker_row1::signature read_some_rows_row1;
|
||||
typename netfun_maker_row2::signature read_some_rows_row2;
|
||||
const char* name;
|
||||
} all_fns[] = {
|
||||
{netfun_maker_row1::sync_errc(&test_connection::read_some_rows),
|
||||
netfun_maker_row2::sync_errc(&test_connection::read_some_rows),
|
||||
"sync" },
|
||||
{netfun_maker_row1::async_diag(&test_connection::async_read_some_rows),
|
||||
netfun_maker_row2::async_diag(&test_connection::async_read_some_rows),
|
||||
"async"},
|
||||
};
|
||||
|
||||
struct fixture
|
||||
{
|
||||
state_t st;
|
||||
test_connection conn;
|
||||
any_connection conn{create_test_any_connection()};
|
||||
std::array<row1, 3> storage1;
|
||||
std::array<row2, 3> storage2;
|
||||
|
||||
test_stream& stream() noexcept { return conn.stream(); }
|
||||
span<row1> storage1_span() { return storage1; }
|
||||
span<row2> storage2_span() { return storage2; }
|
||||
|
||||
test_stream& stream() noexcept { return get_stream(conn); }
|
||||
|
||||
void add_ok() { ::add_ok(get_iface(st), ok_builder().more_results(true).build()); }
|
||||
|
||||
@ -87,99 +71,83 @@ struct fixture
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(repeated_row_types)
|
||||
BOOST_FIXTURE_TEST_CASE(repeated_row_types, fixture)
|
||||
{
|
||||
for (const auto& fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
fixture fix;
|
||||
fix.add_meta_row1();
|
||||
add_meta_row1();
|
||||
|
||||
// 1st resultset: row1
|
||||
fix.stream()
|
||||
.add_bytes(create_text_row_message(0, 10, 4.2f))
|
||||
.add_bytes(create_text_row_message(1, 11, 4.3f));
|
||||
// 1st resultset: row1
|
||||
stream().add_bytes(create_text_row_message(0, 10, 4.2f)).add_bytes(create_text_row_message(1, 11, 4.3f));
|
||||
|
||||
std::size_t num_rows = fns.read_some_rows_row1(fix.conn, fix.st, fix.storage1).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 2u);
|
||||
BOOST_TEST((fix.storage1[0] == row1{10, 4.2f}));
|
||||
BOOST_TEST((fix.storage1[1] == row1{11, 4.3f}));
|
||||
std::size_t num_rows = conn.async_read_some_rows(st, storage1_span(), as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 2u);
|
||||
BOOST_TEST((storage1[0] == row1{10, 4.2f}));
|
||||
BOOST_TEST((storage1[1] == row1{11, 4.3f}));
|
||||
|
||||
// Advance resultset
|
||||
fix.add_ok();
|
||||
fix.add_meta_row1();
|
||||
BOOST_TEST_REQUIRE(fix.st.should_read_rows());
|
||||
// Advance resultset
|
||||
add_ok();
|
||||
add_meta_row1();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
|
||||
// 2nd resultset: row1 again
|
||||
fix.stream().add_bytes(create_text_row_message(2, 13, 0.2f));
|
||||
num_rows = fns.read_some_rows_row1(fix.conn, fix.st, fix.storage1).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((fix.storage1[0] == row1{13, 0.2f}));
|
||||
// 2nd resultset: row1 again
|
||||
stream().add_bytes(create_text_row_message(2, 13, 0.2f));
|
||||
num_rows = conn.async_read_some_rows(st, storage1_span(), as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((storage1[0] == row1{13, 0.2f}));
|
||||
|
||||
// Advance resultset
|
||||
fix.add_ok();
|
||||
fix.add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(fix.st.should_read_rows());
|
||||
// Advance resultset
|
||||
add_ok();
|
||||
add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
|
||||
// 3rd resultset: row2
|
||||
fix.stream().add_bytes(create_text_row_message(3, 9.1));
|
||||
num_rows = fns.read_some_rows_row2(fix.conn, fix.st, fix.storage2).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((fix.storage2[0] == row2{9.1}));
|
||||
// 3rd resultset: row2
|
||||
stream().add_bytes(create_text_row_message(3, 9.1));
|
||||
num_rows = conn.async_read_some_rows(st, storage2_span(), as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((storage2[0] == row2{9.1}));
|
||||
|
||||
// Advance resultset
|
||||
fix.add_ok();
|
||||
fix.add_meta_row1();
|
||||
BOOST_TEST_REQUIRE(fix.st.should_read_rows());
|
||||
// Advance resultset
|
||||
add_ok();
|
||||
add_meta_row1();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
|
||||
// 4th resultset: row1
|
||||
fix.stream().add_bytes(create_text_row_message(4, 43, 0.7f));
|
||||
num_rows = fns.read_some_rows_row1(fix.conn, fix.st, fix.storage1).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((fix.storage1[0] == row1{43, 0.7f}));
|
||||
// 4th resultset: row1
|
||||
stream().add_bytes(create_text_row_message(4, 43, 0.7f));
|
||||
num_rows = conn.async_read_some_rows(st, storage1_span(), as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((storage1[0] == row1{43, 0.7f}));
|
||||
|
||||
// Advance resultset
|
||||
fix.add_ok();
|
||||
fix.add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(fix.st.should_read_rows());
|
||||
// Advance resultset
|
||||
add_ok();
|
||||
add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
|
||||
// 5th resultset: row2
|
||||
fix.stream().add_bytes(create_text_row_message(5, 99.9));
|
||||
num_rows = fns.read_some_rows_row2(fix.conn, fix.st, fix.storage2).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((fix.storage2[0] == row2{99.9}));
|
||||
}
|
||||
}
|
||||
// 5th resultset: row2
|
||||
stream().add_bytes(create_text_row_message(5, 99.9));
|
||||
num_rows = conn.async_read_some_rows(st, storage2_span(), as_netresult).get();
|
||||
BOOST_TEST_REQUIRE(num_rows == 1u);
|
||||
BOOST_TEST((storage2[0] == row2{99.9}));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(error_row_type_mismatch)
|
||||
BOOST_FIXTURE_TEST_CASE(error_row_type_mismatch, fixture)
|
||||
{
|
||||
for (const auto& fns : all_fns)
|
||||
{
|
||||
BOOST_TEST_CONTEXT(fns.name)
|
||||
{
|
||||
fixture fix;
|
||||
fix.add_meta_row1();
|
||||
add_meta_row1();
|
||||
|
||||
// 1st resultset: row1. Note that this will consume the message
|
||||
fix.stream().add_bytes(create_text_row_message(0, 10, 4.2f));
|
||||
fns.read_some_rows_row2(fix.conn, fix.st, fix.storage2)
|
||||
.validate_error(client_errc::row_type_mismatch);
|
||||
// 1st resultset: row1. Note that this will consume the message
|
||||
stream().add_bytes(create_text_row_message(0, 10, 4.2f));
|
||||
conn.async_read_some_rows(st, storage2_span(), as_netresult)
|
||||
.validate_error(client_errc::row_type_mismatch);
|
||||
|
||||
// Advance resultset
|
||||
fix.add_ok();
|
||||
fix.add_meta_row1();
|
||||
fix.add_ok();
|
||||
fix.add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(fix.st.should_read_rows());
|
||||
// Advance resultset
|
||||
add_ok();
|
||||
add_meta_row1();
|
||||
add_ok();
|
||||
add_meta_row2();
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
|
||||
// 3rd resultset: row2
|
||||
fix.stream().add_bytes(create_text_row_message(1, 9.1));
|
||||
fns.read_some_rows_row1(fix.conn, fix.st, fix.storage1)
|
||||
.validate_error(client_errc::row_type_mismatch);
|
||||
}
|
||||
}
|
||||
// 3rd resultset: row2
|
||||
stream().add_bytes(create_text_row_message(1, 9.1));
|
||||
conn.async_read_some_rows(st, storage1_span(), as_netresult)
|
||||
.validate_error(client_errc::row_type_mismatch);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
x
Reference in New Issue
Block a user