mirror of
https://github.com/boostorg/mysql.git
synced 2025-05-11 13:44:35 +00:00
Added support for the static interface
Added support for the static interface Statement execution now supports bool and optionals Replaced the FieldLikeTuple and FieldLike concepts by WritableFieldTuple and WritableField Added diagnostics::client_message Solved a problem with blob types and stored procedures under MariaDB Removed a troublesome assert in row_impl Protected numeric_limits min/max from intrusive macros Added C++11/14 order management examples Documentation section collapsing Test Jamfiles now show output only on failure close #60 close #153 close #154
This commit is contained in:
parent
c3af9e3a58
commit
b51115668d
@ -26,21 +26,21 @@ AlignAfterOpenBracket: BlockIndent
|
||||
PointerAlignment: Left
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<boost/mysql/[a-zA-Z0-9_]*\.hpp>'
|
||||
- Regex: '^<boost/mysql/detail/.*>|^<boost/mysql/impl/.*'
|
||||
Priority: -9
|
||||
SortPriority: 0
|
||||
SortPriority: 1
|
||||
- Regex: '^<boost/mysql.*\.hpp>'
|
||||
Priority: -8
|
||||
SortPriority: 0
|
||||
- Regex: '^<boost/.*\.hpp>'
|
||||
Priority: -7
|
||||
SortPriority: 0
|
||||
SortPriority: 2
|
||||
- Regex: "^<.*"
|
||||
Priority: -6
|
||||
SortPriority: 0
|
||||
SortPriority: 3
|
||||
- Regex: ".*"
|
||||
Priority: -5
|
||||
SortPriority: 0
|
||||
SortPriority: 4
|
||||
IndentCaseLabels: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
|
@ -62,6 +62,7 @@ else()
|
||||
Boost::assert
|
||||
Boost::config
|
||||
Boost::core
|
||||
Boost::describe
|
||||
Boost::endian
|
||||
Boost::lexical_cast
|
||||
Boost::mp11
|
||||
|
@ -10,6 +10,11 @@ Boost.MySQL is part of Boost.
|
||||
|
||||
Boost.MySQL gets its first stable release with Boost 1.82 on the 12th of April, 2023.
|
||||
|
||||
## Feedback
|
||||
|
||||
Do you have any suggestion? Would you like to share a bad or good experience while using the library?
|
||||
Please comment [on this issue](https://github.com/boostorg/mysql/issues/140).
|
||||
|
||||
## Why another MySQL C++ client?
|
||||
|
||||
- It is fully compatible with Boost.Asio and integrates well with any other
|
||||
|
@ -45,9 +45,12 @@ docca.reference reference.qbk
|
||||
\"BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(ex)=\" \\
|
||||
\"protected=private\" \\
|
||||
\"BOOST_CXX14_CONSTEXPR=constexpr\" \\
|
||||
\"BOOST_MYSQL_FIELD_LIKE_TUPLE=class\" \\
|
||||
\"BOOST_MYSQL_CXX14\" \\
|
||||
\"BOOST_MYSQL_WRITABLE_FIELD_TUPLE=class\" \\
|
||||
\"BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR=class\" \\
|
||||
\"BOOST_MYSQL_EXECUTION_REQUEST=class\" \\
|
||||
\"BOOST_MYSQL_RESULTS_TYPE=class\" \\
|
||||
\"BOOST_MYSQL_EXECUTION_STATE_TYPE=class\" \\
|
||||
"
|
||||
<doxygen:param>SKIP_FUNCTION_MACROS=NO
|
||||
<doxygen:param>OUTPUT_LANGUAGE=English
|
||||
|
4
doc/images/results.svg
Normal file
4
doc/images/results.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 18 KiB |
@ -39,8 +39,11 @@
|
||||
[def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html ['Executor]]]
|
||||
[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]]
|
||||
[def __FieldViewFwdIterator__ [reflink2 FieldViewFwdIterator ['FieldViewFwdIterator]]]
|
||||
[def __FieldLikeTuple__ [reflink2 FieldLikeTuple ['FieldLikeTuple]]]
|
||||
[def __WritableFieldTuple__ [reflink2 WritableFieldTuple ['WritableFieldTuple]]]
|
||||
[def __ExecutionRequest__ [reflink2 ExecutionRequest ['ExecutionRequest]]]
|
||||
[def __StaticRow__ [reflink2 StaticRow ['StaticRow]]]
|
||||
[def __ResultsType__ [reflink2 ResultsType ['ResultsType]]]
|
||||
[def __ExecutionStateType__ [reflink2 ExecutionStateType ['ExecutionStateType]]]
|
||||
|
||||
[def __Boost__ [@https://www.boost.org/ Boost]]
|
||||
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
|
||||
@ -85,37 +88,72 @@
|
||||
[def __GEOMETRY__ [mysqllink spatial-type-overview.html `GEOMETRY`]]
|
||||
[def __USE__ [mysqllink use.html `USE`]]
|
||||
|
||||
[/ Taken db_setup.sql, because import doesn't work for SQL files - keep in sync.
|
||||
Having them in a separate file doesn't work ]
|
||||
[def __sp_get_employees__
|
||||
```
|
||||
CREATE PROCEDURE get_employees(IN pin_company_id CHAR(10))
|
||||
BEGIN
|
||||
START TRANSACTION READ ONLY;
|
||||
SELECT id, name, tax_id FROM company WHERE id = pin_company_id;
|
||||
SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id;
|
||||
COMMIT;
|
||||
END
|
||||
```]
|
||||
|
||||
[def __sp_create_employee__
|
||||
```
|
||||
CREATE PROCEDURE create_employee(
|
||||
IN pin_company_id CHAR(10),
|
||||
IN pin_first_name VARCHAR(100),
|
||||
IN pin_last_name VARCHAR(100),
|
||||
OUT pout_employee_id INT
|
||||
)
|
||||
BEGIN
|
||||
START TRANSACTION;
|
||||
INSERT INTO employee (company_id, first_name, last_name)
|
||||
VALUES (pin_company_id, pin_first_name, pin_last_name);
|
||||
SET pout_employee_id = LAST_INSERT_ID();
|
||||
INSERT INTO audit_log (msg) VALUES ('Created new employee...');
|
||||
COMMIT;
|
||||
END
|
||||
```]
|
||||
|
||||
[import ../../example/snippets.cpp]
|
||||
|
||||
[include 01_intro.qbk]
|
||||
[include 02_tutorial.qbk]
|
||||
[include 03_overview.qbk]
|
||||
[include 04_queries.qbk]
|
||||
[include 05_prepared_statements.qbk]
|
||||
[include 06_multi_resultset.qbk]
|
||||
[include 07_multi_function.qbk]
|
||||
[include 08_fields.qbk]
|
||||
[include 09_metadata.qbk]
|
||||
[include 10_async.qbk]
|
||||
[include 11_ssl.qbk]
|
||||
[include 12_other_streams.qbk]
|
||||
[include 13_error_handling.qbk]
|
||||
[include 14_connparams.qbk]
|
||||
[include 15_reconnecting.qbk]
|
||||
[include 16_charsets.qbk]
|
||||
[include 17_examples.qbk]
|
||||
[include 18_tests.qbk]
|
||||
[include 04_dynamic_interface.qbk]
|
||||
[include 05_static_interface.qbk]
|
||||
[include 06_queries.qbk]
|
||||
[include 07_prepared_statements.qbk]
|
||||
[include 08_multi_resultset.qbk]
|
||||
[include 09_multi_function.qbk]
|
||||
[include 10_metadata.qbk]
|
||||
[include 11_async.qbk]
|
||||
[include 12_ssl.qbk]
|
||||
[include 13_other_streams.qbk]
|
||||
[include 14_error_handling.qbk]
|
||||
[include 15_connparams.qbk]
|
||||
[include 16_reconnecting.qbk]
|
||||
[include 17_charsets.qbk]
|
||||
[include 18_time_types.qbk]
|
||||
[include 19_examples.qbk]
|
||||
[include 20_tests.qbk]
|
||||
|
||||
|
||||
[section:ref Reference]
|
||||
[xinclude helpers/quickref.xml]
|
||||
[block'''<part label="Two: Reference">''']
|
||||
[include reference.qbk]
|
||||
[include helpers/Stream.qbk]
|
||||
[include helpers/SocketStream.qbk]
|
||||
[include helpers/FieldViewFwdIterator.qbk]
|
||||
[include helpers/FieldLikeTuple.qbk]
|
||||
[include helpers/ExecutionRequest.qbk]
|
||||
[include helpers/ExecutionStateType.qbk]
|
||||
[include helpers/FieldViewFwdIterator.qbk]
|
||||
[include helpers/ResultsType.qbk]
|
||||
[include helpers/SocketStream.qbk]
|
||||
[include helpers/StaticRow.qbk]
|
||||
[include helpers/Stream.qbk]
|
||||
[include helpers/WritableFieldTuple.qbk]
|
||||
[block'''</part>''']
|
||||
[endsect]
|
||||
|
@ -95,214 +95,102 @@ You can access both using [refmem connection execute]:
|
||||
]
|
||||
]
|
||||
|
||||
When you execute a text query or a prepared statement, you get a `results` object, which will be the subject
|
||||
of the next section. We will delve deeper into prepared statements [link mysql.overview.statements later].
|
||||
[endsect]
|
||||
|
||||
[section The dynamic and the static interfaces]
|
||||
|
||||
There are two different interfaces to access the rows generated by a query or statement.
|
||||
You can use the [reflink results] class to access rows using a dynamically-typed interface,
|
||||
using variant-like objects to represent values retrieved from the server. On ther other hand,
|
||||
[reflink static_results] is statically-typed. You specify the shape of your rows at compile-time,
|
||||
and the library will parse the retrieved values for you into the types you provide.
|
||||
|
||||
You can use almost every feature in this library (including text queries and prepared statements) with both interfaces.
|
||||
|
||||
For example, given the following table:
|
||||
|
||||
[overview_ifaces_table]
|
||||
|
||||
This is how you would access its contents using either of the interfaces:
|
||||
|
||||
[table
|
||||
[
|
||||
[Interface]
|
||||
[Description]
|
||||
[Example]
|
||||
]
|
||||
[
|
||||
[
|
||||
Dynamic interface: [reflink results]
|
||||
]
|
||||
[
|
||||
Variant based[br]
|
||||
Available in C++11[br]
|
||||
[link mysql.dynamic_interface Learn more][br]
|
||||
[link mysql.examples.prepared_statements_cpp11 Example code]
|
||||
]
|
||||
[
|
||||
[overview_ifaces_dynamic]
|
||||
]
|
||||
]
|
||||
[
|
||||
[
|
||||
Static interface: [reflink static_results]
|
||||
]
|
||||
[
|
||||
Parses rows into your own types[br]
|
||||
Requires C++14[br]
|
||||
[link mysql.static_interface Learn more][br]
|
||||
[link mysql.examples.prepared_statements_cpp14 Example code]
|
||||
]
|
||||
[
|
||||
[describe_post][br]
|
||||
[overview_ifaces_static]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Resultsets]
|
||||
|
||||
In MySQL, a ['resultset] referes to the results generated by a SQL query. The [reflink results] class
|
||||
is an in-memory reprentation of a MySQL resultset. The following diagram shows an approximate representation
|
||||
of what a resultset looks like:
|
||||
In MySQL, a ['resultset] referes to the results generated by a SQL query. When you execute a text query
|
||||
or a prepared statement, you get back a resultset. We've already been using resultsets: the [reflink results]
|
||||
and [reflink static_results] classes are in-memory representations of a resultset.
|
||||
|
||||
[$mysql/images/resultset.svg [align center] [scale 125]]
|
||||
A resultset is composed of three pieces of data:
|
||||
|
||||
We can see that a resultset is composed of three pieces of information:
|
||||
|
||||
* The actual rows generated by the SQL query: [refmem results rows]. We'll expand on this later.
|
||||
* Metadata about the columns retrieved by the query: [refmem results meta]. See [link mysql.meta this section]
|
||||
for more info.
|
||||
* Additional information about the query execution, like the number of affected rows ([refmem results affected_rows])
|
||||
or the number of warnings generated by the query ([refmem results warning_count]).
|
||||
|
||||
You can obtain a `results` object by executing a text query or a prepared statement, by calling
|
||||
[refmem connection execute].
|
||||
[variablelist
|
||||
[
|
||||
[Rows]
|
||||
[
|
||||
The actual rows generated by the SQL query: [refmem results rows] and [refmem static_results rows].
|
||||
]
|
||||
]
|
||||
[
|
||||
[Metadata]
|
||||
[
|
||||
Information about the columns retrieved by the query: [refmem results meta] and [refmem static_results meta].
|
||||
There is one object per retrieved column. It provides information about column names, types, uniqueness contraints...
|
||||
]
|
||||
]
|
||||
[
|
||||
[Additional execution information]
|
||||
[
|
||||
Extra info on the execution of the operation, like the number of affected rows ([refmem results affected_rows]
|
||||
and [refmem static_results affected_rows]) or the last auto-generated ID for `INSERT` statements ([refmem results last_insert_id]
|
||||
and [refmem static_results last_insert_id]).
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
All SQL statements generate resultsets. Statements that generate no rows, like `INSERT`s, generate empty resultsets
|
||||
(i.e. `result.rows().empty() == true`). The interface to execute `SELECT`s and `INSERT`s is the same.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Rows and fields]
|
||||
|
||||
We saw that [refmem results rows] returns a matrix-like data structure containing the rows
|
||||
retrieved by SQL query. This library defines six data structures to represent MySQL data:
|
||||
|
||||
[variablelist
|
||||
[
|
||||
[[reflink field]]
|
||||
[The smallest unit of data. A single "cell" in a MySQL table. This is an owning, variant-like type.]
|
||||
]
|
||||
[
|
||||
[[reflink field_view]]
|
||||
[Like `field`, but non-owning.]
|
||||
]
|
||||
[
|
||||
[[reflink row]]
|
||||
[An owning, `vector`-like collection of fields.]
|
||||
]
|
||||
[
|
||||
[[reflink row_view]]
|
||||
[Like `row`, but non-owning.]
|
||||
]
|
||||
[
|
||||
[[reflink rows]]
|
||||
[An owning, matrix-like collection of fields. Represents several rows of the same size in an optimized way.]
|
||||
]
|
||||
[
|
||||
[[reflink rows_view]]
|
||||
[Like `rows`, but non-owning.]
|
||||
]
|
||||
]
|
||||
|
||||
[refmem results rows] returns a [reflink rows_view] object. The memory for the rows is owned by the
|
||||
`results` object. Indexing the returned view also returns view objects:
|
||||
|
||||
[overview_views]
|
||||
|
||||
Views behave similarly to `std::string_view`. You must make sure that you don't use a view after the
|
||||
storage it points to has gone out of scope. In this case, you must not use any of the views after the
|
||||
`results` object has gone out of scope.
|
||||
|
||||
As it happens with `std::string_view`, you can take ownership of a view using its owning counterpart:
|
||||
|
||||
[overview_taking_ownership]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:fields Using fields]
|
||||
|
||||
[reflink field] and [reflink field_view] are specialized variant-like types that can hold any type
|
||||
you may find in a MySQL table. Once you obtain a field, you can access its contents using the following functions:
|
||||
|
||||
* You can query a field's type by using [refmemunq field_view kind],
|
||||
which returns a [reflink field_kind] enum.
|
||||
* You can query whether a field contains a certain type with `field::is_xxx`.
|
||||
* You can get the underlying value with `field::as_xxx` and `field::get_xxx`.
|
||||
The `as_xxx` functions are checked (they will throw an exception if the
|
||||
actual type doesn't match), while the `get_xxx` are unchecked (they result
|
||||
in undefined behavior on type mismatch).
|
||||
* You can stream fields and compare them for equality.
|
||||
|
||||
For example:
|
||||
|
||||
[overview_using_fields]
|
||||
|
||||
`NULL` values are represented as field objects having `kind() == field_kind::null`.
|
||||
You can check whether a value is `NULL` or not using [refmemunq field_view is_null].
|
||||
This is how `NULL`s are typically handled:
|
||||
|
||||
[overview_handling_nulls]
|
||||
|
||||
Every MySQL type is mapped to a single C++ type. The following table shows these mappings:
|
||||
|
||||
[table:accessors
|
||||
[
|
||||
[`field_kind`]
|
||||
[C++ type]
|
||||
[MySQL types]
|
||||
[`is` accessor]
|
||||
[`as` accessor]
|
||||
[`get` accessor]
|
||||
]
|
||||
[
|
||||
[`int64`]
|
||||
[`std::int64_t`]
|
||||
[__TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__]
|
||||
[[refmemunq field_view is_int64]]
|
||||
[[refmemunq field_view as_int64]]
|
||||
[[refmemunq field_view get_int64]]
|
||||
]
|
||||
[
|
||||
[`uint64`]
|
||||
[`std::uint64_t`]
|
||||
[Unsigned __TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__, __YEAR__, __BIT__]
|
||||
[[refmemunq field_view is_uint64]]
|
||||
[[refmemunq field_view as_uint64]]
|
||||
[[refmemunq field_view get_uint64]]
|
||||
]
|
||||
[
|
||||
[`string`]
|
||||
[
|
||||
[reflink string_view] for `field_view`
|
||||
|
||||
`std::string` for `field`
|
||||
]
|
||||
[
|
||||
__CHAR__, __VARCHAR__, __TEXT__ (all sizes), __ENUM__, __SET__, __DECIMAL__, __NUMERIC__, __JSON__
|
||||
]
|
||||
[[refmemunq field_view is_string]]
|
||||
[[refmemunq field_view as_string]]
|
||||
[[refmemunq field_view get_string]]
|
||||
]
|
||||
[
|
||||
[`blob`]
|
||||
[
|
||||
[reflink blob_view] for `field_view`
|
||||
|
||||
[reflink blob] for `field`
|
||||
]
|
||||
[__BINARY__, __VARBINARY__, __BLOB__ (all sizes), __GEOMETRY__]
|
||||
[[refmemunq field_view is_blob]]
|
||||
[[refmemunq field_view as_blob]]
|
||||
[[refmemunq field_view get_blob]]
|
||||
]
|
||||
[
|
||||
[`float_`]
|
||||
[`float`]
|
||||
[__FLOAT__]
|
||||
[[refmemunq field_view is_float]]
|
||||
[[refmemunq field_view as_float]]
|
||||
[[refmemunq field_view get_float]]
|
||||
]
|
||||
[
|
||||
[`double_`]
|
||||
[`double`]
|
||||
[__DOUBLE__]
|
||||
[[refmemunq field_view is_double]]
|
||||
[[refmemunq field_view as_double]]
|
||||
[[refmemunq field_view get_double]]
|
||||
]
|
||||
[
|
||||
[`date`]
|
||||
[[reflink date]]
|
||||
[__DATE__]
|
||||
[[refmemunq field_view is_date]]
|
||||
[[refmemunq field_view as_date]]
|
||||
[[refmemunq field_view get_date]]
|
||||
]
|
||||
[
|
||||
[`datetime`]
|
||||
[[reflink datetime]]
|
||||
[__DATETIME__, __TIMESTAMP__]
|
||||
[[refmemunq field_view is_datetime]]
|
||||
[[refmemunq field_view as_datetime]]
|
||||
[[refmemunq field_view get_datetime]]
|
||||
]
|
||||
[
|
||||
[`time`]
|
||||
[[reflink time]]
|
||||
[__TIME__]
|
||||
[[refmemunq field_view is_time]]
|
||||
[[refmemunq field_view as_time]]
|
||||
[[refmemunq field_view get_time]]
|
||||
]
|
||||
[
|
||||
[`null`]
|
||||
[]
|
||||
[Any of the above, when they're `NULL`]
|
||||
[[refmemunq field_view is_null]]
|
||||
[]
|
||||
[]
|
||||
]
|
||||
]
|
||||
|
||||
[link mysql.fields This section] contains more information about fields.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:statements Using prepared statements]
|
||||
[section:statements Prepared statements]
|
||||
|
||||
Until now, we've used simple text queries that did not contain any user-provided input.
|
||||
In the real world, most queries will contain some piece of user-provided input.
|
||||
@ -335,34 +223,6 @@ To learn more about prepared statements, please refer to [link mysql.prepared_st
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Multi-function operations]
|
||||
|
||||
Until now, we've been using [refmem connection execute], which
|
||||
executes some SQL and reads all generated data into an in-memory `results` object.
|
||||
|
||||
Some use cases may not fit in this simple pattern. For example:
|
||||
|
||||
* When reading a very big resultset, it may not be efficient (or even possible) to completely
|
||||
load it in memory. Reading rows in batches may be more adequate.
|
||||
* If rows contain very long `TEXT` or `BLOB` fields, it may not be adequate to copy these values
|
||||
from the network buffer into the `results` object. A view-based approach may be better.
|
||||
|
||||
For these cases, we can break the `execute()` operation into several steps,
|
||||
using a ['multi-function operation] (the term is coined by this library). This example reads an entire
|
||||
table in batches, which can be the case in an ETL process:
|
||||
|
||||
[overview_multifn]
|
||||
|
||||
[warning
|
||||
Once you start a multi-function operation with [refmem connection start_execution],
|
||||
the server immediately sends all rows to the client. [*You must read all rows] before engaging in further operations.
|
||||
Otherwise, you will encounter packet mismatches, which can lead to bugs and vulnerabilities!
|
||||
]
|
||||
|
||||
Multi-function operations are powerful but complex. Only use them when there is a strong reason to do so.
|
||||
Please refer to [link mysql.multi_function this section] for more information on these operations.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:errors Error handling]
|
||||
|
||||
@ -410,4 +270,35 @@ If you need to perform queries in parallel, open more connections to the server.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Multi-function operations]
|
||||
|
||||
Until now, we've been using [refmem connection execute], which
|
||||
executes some SQL and reads all generated data into an in-memory object.
|
||||
|
||||
Some use cases may not fit in this simple pattern. For example:
|
||||
|
||||
* When reading a very big resultset, it may not be efficient (or even possible) to completely
|
||||
load it in memory. Reading rows in batches may be more adequate.
|
||||
* If rows contain very long `TEXT` or `BLOB` fields, it may not be adequate to copy these values
|
||||
from the network buffer into the `results` object. A view-based approach may be better.
|
||||
|
||||
For these cases, we can break the `execute()` operation into several steps,
|
||||
using a ['multi-function operation] (the term is coined by this library). This example reads an entire
|
||||
table in batches, which can be the case in an ETL process:
|
||||
|
||||
[overview_multifn]
|
||||
|
||||
[warning
|
||||
Once you start a multi-function operation with [refmem connection start_execution],
|
||||
the server immediately sends all rows to the client. [*You must read all rows] before engaging in further operations.
|
||||
Otherwise, you will encounter packet mismatches, which can lead to bugs and vulnerabilities!
|
||||
]
|
||||
|
||||
Multi-function operations are powerful but complex. Only use them when there is a strong reason to do so.
|
||||
Multi-function operations also work with the static interface.
|
||||
Please refer to [link mysql.multi_function this section] for more information on these operations.
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[endsect]
|
||||
|
@ -5,36 +5,207 @@
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
|
||||
[section:fields Fields]
|
||||
[section:dynamic_interface The dynamic interface]
|
||||
[nochunk]
|
||||
|
||||
This section delves deeper on how to use [reflink field] and [reflink field_view]
|
||||
and its underlying types. Please make sure you've read [link mysql.overview.fields this section]
|
||||
before going on.
|
||||
To use the dynamic interface, use the [reflink results] class. `results` is
|
||||
an in-memory representation of a resultset. We can depict it like this
|
||||
(this is actually a simplified representation, since
|
||||
[link mysql.multi_resultset some statements may return more than one resultset]).
|
||||
|
||||
[section field_view vs field]
|
||||
[$mysql/images/results.svg [align center] [scale 125]]
|
||||
|
||||
This library implements two types representing the concept of a field: [reflink field_view], which is non-owning,
|
||||
and [reflink field], which is owning. The relationship between them is similar to the one between
|
||||
`std::string_view` and `std::string`.
|
||||
We can see that [refmem results rows] returns a matrix-like object,
|
||||
containing the retrieved rows. This section is dedicated on diving deeper
|
||||
on how to use these objects.
|
||||
|
||||
For efficiency reasons, all library functions return `field_view`s. For example:
|
||||
[heading Rows and fields]
|
||||
|
||||
[fields_field_views]
|
||||
This matrix-like structure is composed of variant-like objects called ['fields].
|
||||
Field objects are capable of representing any value retrieved from MySQL.
|
||||
|
||||
When dealing with scalars (anything that is neither a string nor a blob), `field_view`'s accessors make
|
||||
a copy of the scalar:
|
||||
This library defines the following classes to work with rows and fields:
|
||||
|
||||
[fields_field_views_scalars]
|
||||
[variablelist
|
||||
[
|
||||
[[reflink field]]
|
||||
[The smallest unit of data. A single "cell" in a MySQL table. This is an owning, variant-like type.]
|
||||
]
|
||||
[
|
||||
[[reflink field_view]]
|
||||
[Like `field`, but non-owning.]
|
||||
]
|
||||
[
|
||||
[[reflink row]]
|
||||
[An owning, `vector`-like collection of fields.]
|
||||
]
|
||||
[
|
||||
[[reflink row_view]]
|
||||
[Like `row`, but non-owning.]
|
||||
]
|
||||
[
|
||||
[[reflink rows]]
|
||||
[An owning, matrix-like collection of fields. Represents several rows of the same size in an optimized way.]
|
||||
]
|
||||
[
|
||||
[[reflink rows_view]]
|
||||
[Like `rows`, but non-owning.]
|
||||
]
|
||||
]
|
||||
|
||||
[refmem results rows] returns a [reflink rows_view] object. The memory for the rows is owned by the
|
||||
`results` object. Indexing the returned view also returns view objects:
|
||||
|
||||
[dynamic_views]
|
||||
|
||||
Views behave similarly to `std::string_view`. You must make sure that you don't use a view after the
|
||||
storage it points to has gone out of scope. In this case, you must not use any of the views after the
|
||||
`results` object has gone out of scope.
|
||||
|
||||
As it happens with `std::string_view`, you can take ownership of a view using its owning counterpart:
|
||||
|
||||
[dynamic_taking_ownership]
|
||||
|
||||
[heading Using fields]
|
||||
|
||||
[reflink field] and [reflink field_view] are specialized variant-like types that can hold any type
|
||||
you may find in a MySQL table. Once you obtain a field, you can access its contents using the following functions:
|
||||
|
||||
* You can query a field's type by using [refmemunq field_view kind],
|
||||
which returns a [reflink field_kind] enum.
|
||||
* You can query whether a field contains a certain type with `field::is_xxx`.
|
||||
* You can get the underlying value with `field::as_xxx` and `field::get_xxx`.
|
||||
The `as_xxx` functions are checked (they will throw an exception if the
|
||||
actual type doesn't match), while the `get_xxx` are unchecked (they result
|
||||
in undefined behavior on type mismatch).
|
||||
* You can stream fields and compare them for equality.
|
||||
|
||||
For example:
|
||||
|
||||
[dynamic_using_fields]
|
||||
|
||||
`NULL` values are represented as field objects having `kind() == field_kind::null`.
|
||||
You can check whether a value is `NULL` or not using [refmemunq field_view is_null].
|
||||
This is how `NULL`s are typically handled:
|
||||
|
||||
[dynamic_handling_nulls]
|
||||
|
||||
[heading MySQL to C++ type mappings]
|
||||
|
||||
Every MySQL type is mapped to a single C++ type. The following table shows these mappings:
|
||||
|
||||
[table:accessors
|
||||
[
|
||||
[`field_kind`]
|
||||
[C++ type]
|
||||
[MySQL types]
|
||||
[`is` accessor]
|
||||
[`as` accessor]
|
||||
[`get` accessor]
|
||||
]
|
||||
[
|
||||
[`int64`]
|
||||
[`std::int64_t`]
|
||||
[__TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__]
|
||||
[[refmemunq field_view is_int64]]
|
||||
[[refmemunq field_view as_int64]]
|
||||
[[refmemunq field_view get_int64]]
|
||||
]
|
||||
[
|
||||
[`uint64`]
|
||||
[`std::uint64_t`]
|
||||
[Unsigned __TINYINT__, __SMALLINT__, __MEDIUMINT__, __INT__, __BIGINT__, __YEAR__, __BIT__]
|
||||
[[refmemunq field_view is_uint64]]
|
||||
[[refmemunq field_view as_uint64]]
|
||||
[[refmemunq field_view get_uint64]]
|
||||
]
|
||||
[
|
||||
[`string`]
|
||||
[
|
||||
[reflink string_view] for `field_view`
|
||||
|
||||
`std::string` for `field`
|
||||
]
|
||||
[
|
||||
__CHAR__, __VARCHAR__, __TEXT__ (all sizes), __ENUM__, __SET__, __DECIMAL__, __NUMERIC__, __JSON__
|
||||
]
|
||||
[[refmemunq field_view is_string]]
|
||||
[[refmemunq field_view as_string]]
|
||||
[[refmemunq field_view get_string]]
|
||||
]
|
||||
[
|
||||
[`blob`]
|
||||
[
|
||||
[reflink blob_view] for `field_view`
|
||||
|
||||
[reflink blob] for `field`
|
||||
]
|
||||
[__BINARY__, __VARBINARY__, __BLOB__ (all sizes), __GEOMETRY__]
|
||||
[[refmemunq field_view is_blob]]
|
||||
[[refmemunq field_view as_blob]]
|
||||
[[refmemunq field_view get_blob]]
|
||||
]
|
||||
[
|
||||
[`float_`]
|
||||
[`float`]
|
||||
[__FLOAT__]
|
||||
[[refmemunq field_view is_float]]
|
||||
[[refmemunq field_view as_float]]
|
||||
[[refmemunq field_view get_float]]
|
||||
]
|
||||
[
|
||||
[`double_`]
|
||||
[`double`]
|
||||
[__DOUBLE__]
|
||||
[[refmemunq field_view is_double]]
|
||||
[[refmemunq field_view as_double]]
|
||||
[[refmemunq field_view get_double]]
|
||||
]
|
||||
[
|
||||
[`date`]
|
||||
[[reflink date]]
|
||||
[__DATE__]
|
||||
[[refmemunq field_view is_date]]
|
||||
[[refmemunq field_view as_date]]
|
||||
[[refmemunq field_view get_date]]
|
||||
]
|
||||
[
|
||||
[`datetime`]
|
||||
[[reflink datetime]]
|
||||
[__DATETIME__, __TIMESTAMP__]
|
||||
[[refmemunq field_view is_datetime]]
|
||||
[[refmemunq field_view as_datetime]]
|
||||
[[refmemunq field_view get_datetime]]
|
||||
]
|
||||
[
|
||||
[`time`]
|
||||
[[reflink time]]
|
||||
[__TIME__]
|
||||
[[refmemunq field_view is_time]]
|
||||
[[refmemunq field_view as_time]]
|
||||
[[refmemunq field_view get_time]]
|
||||
]
|
||||
[
|
||||
[`null`]
|
||||
[]
|
||||
[Any of the above, when they're `NULL`]
|
||||
[[refmemunq field_view is_null]]
|
||||
[]
|
||||
[]
|
||||
]
|
||||
]
|
||||
|
||||
No character set conversion is applied on strings. They are provided
|
||||
as the server sends them. If you've issued a `"SET NAMES <charset-name>"` statement,
|
||||
strings will be encoded according to `<charset-name>`. For details, see [link mysql.charsets this section].
|
||||
|
||||
[heading The field class]
|
||||
|
||||
[reflink field_view] is to [reflink field] what `std::string_view` is to `std::string`.
|
||||
|
||||
`field_view`s are cheap to create and to copy, as they are small objects and don't perform
|
||||
any memory allocations. They are also immutable. On the other hand, `field`s may be more
|
||||
expensive to create and copy, as they may perform memory allocations.
|
||||
|
||||
You may create a `field` from a `field_view`, taking ownership of its contents:
|
||||
|
||||
[fields_taking_ownership]
|
||||
expensive to create and copy, as they may perform memory allocations. `field`s are mutable.
|
||||
|
||||
`field` and `field_view` use the same underlying types for scalars. For strings and blobs,
|
||||
`field` uses the owning types `std::string` and [reflink blob], while `field_view` uses the
|
||||
@ -42,157 +213,27 @@ reference types [reflink string_view] and [reflink blob_view].
|
||||
|
||||
`field` accessors return references, which allow you to mutate the underlying object:
|
||||
|
||||
[field_accessor_references]
|
||||
[dynamic_field_accessor_references]
|
||||
|
||||
You can also mutate a `field` using the assignment operator. This allows you to also
|
||||
change the underlying type of a `field`:
|
||||
|
||||
[field_assignment]
|
||||
[dynamic_field_assignment]
|
||||
|
||||
[endsect]
|
||||
[heading Multi-resultset and multi-function operations]
|
||||
|
||||
[section Arithmetic types]
|
||||
You can use both with the dynamic interface. Please refer to the sections
|
||||
on [link mysql.multi_resultset multi-resultset operations] and
|
||||
[link mysql.multi_function multi-function operations] for more information.
|
||||
|
||||
This library uses `std::int64_t` to represent all MySQL signed integers (`TINYINT`, `SMALLINT`, `MEDIUMINT`...).
|
||||
Similarly, unsigned integers use `std::uint64_t`, 32-bit floats use `float` and 64-bit doubles
|
||||
use `double`.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section String types]
|
||||
|
||||
String types (`CHAR`, `VARCHAR`, `TEXT`, `SET`...) are represented as string fields, which
|
||||
translates into [reflink string_view] for `field_view` and `std::string` for `field`.
|
||||
|
||||
This library performs no character set conversion on strings. All strings are provided
|
||||
as the server sends them. If you've issued a `"SET NAMES <charset-name>"` statement,
|
||||
strings will be encoded according to `<charset-name>`. For details, see [link mysql.charsets this section].
|
||||
|
||||
The `JSON` type is represented as a string containing the serialized JSON value.
|
||||
The `DECIMAL` type is also provided as a string, to avoid losing precision.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Blob types]
|
||||
|
||||
The types `BINARY`, `VARBINARY` and `BLOB` (of any size) are represented as blob fields,
|
||||
which are similar to string fields, but represent binary data instead of characters.
|
||||
The following types are used:
|
||||
|
||||
* [reflink blob_view] is used by `field_view`. It's an alias for [@boost:/doc/html/core/span.html `boost::span<const unsigned char>`]
|
||||
(a C++11 backport of `std::span`).
|
||||
* [reflink blob] is used by `field`. It's an alias for `std::vector<unsigned char>`.
|
||||
|
||||
If MySQL adds any other type that this library doesn't understand, it is also represented as a blob.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section The DATE type]
|
||||
|
||||
MySQL __DATE__ is represented as [reflink date] in C++. `date` holds the year, month and day components of a date.
|
||||
It is a type close to the protocol, rather than a vocabulary type. The main reason for using `date` instead
|
||||
of a `std::chrono::time_point` type is that, under certain configurations, MySQL allows storing invalid
|
||||
dates, such as `2020-00-01`. These are not representable as a `std::chrono::time_point`.
|
||||
|
||||
Unless dealing with these special values, we recommend converting `date`s to a `time_point` before using them.
|
||||
The member type [refmem date time_point] is a system-clock `time_point` that can represent the entire
|
||||
MySQL `DATE` range without problems. You can use [refmem date as_time_point] to perform the cast:
|
||||
|
||||
[field_date_as_time_point]
|
||||
|
||||
If the date is not valid, `as_time_point` will throw an exception.
|
||||
|
||||
You can query whether a `date` contains a valid date or not using [refmem date valid]:
|
||||
|
||||
[field_date_valid]
|
||||
|
||||
You can combine it with [refmem date get_time_point], which performs an unchecked
|
||||
conversion:
|
||||
|
||||
[field_date_get_time_point]
|
||||
|
||||
You can also construct a `date` from a `time_point`. If the `time_point` is
|
||||
out of range, an exception is thrown.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section The DATETIME and TIMESTAMP types]
|
||||
|
||||
Both types are represented as [reflink datetime] in C++. `datetime` represents a broken time point,
|
||||
having year, month, day, hour, minute, second and microseconds.
|
||||
|
||||
MySQL also accepts invalid datetimes (like `2020-00-10 10:20:59.000000`). The same considerations for
|
||||
`date` apply:
|
||||
|
||||
[field_datetime]
|
||||
|
||||
The `datetime` object doesn't carry any time zone information with it. The time zone semantics
|
||||
depend on the actual MySQL type:
|
||||
|
||||
* __DATETIME__ is a "naive" time point object. It represents a time point without any time zone
|
||||
information at all. It is up to the user to interpret which time zone the object is in.
|
||||
* When a __TIMESTAMP__ object is inserted, it is interpreted to be in the connection's local time zone,
|
||||
as given by the __time_zone__ variable, and converted to UTC for storage. When retrieved, it is converted back
|
||||
to the time zone indicated by __time_zone__. The retrieved value of a `TIMESTAMP`
|
||||
field is thus a time point in some local time zone, dictated by the current
|
||||
__time_zone__ variable. As this variable can be changed programmatically from SQL, without
|
||||
the library knowing it, we represent `TIMESTAMP`'s using the `datetime` object, which doesn't include time zone information.
|
||||
|
||||
When using `TIMESTAMP`, we recommend setting the __time_zone__ to a known value. To illustrate this,
|
||||
consider an event-logging system with the following table definition:
|
||||
|
||||
[field_timestamp_setup]
|
||||
|
||||
We will be inserting events with an explicit timestamp. We may also want to retrieve events with
|
||||
a timestamp filter. This is what our prepared statements would look like:
|
||||
|
||||
[field_timestamp_stmts]
|
||||
|
||||
These statements may be run from different parts of our code, or even from different applications.
|
||||
To get consistent results, we must make sure that the time zones used during insertion and retrieval are the same.
|
||||
By default, __time_zone__ gets set to `SYSTEM`, which will use the server's time zone settings.
|
||||
This is not what we want here, so let's change it:
|
||||
|
||||
[fields_timestamp_set_time_zone]
|
||||
|
||||
With this, the insertion code can look like:
|
||||
|
||||
[fields_timestamp_insert]
|
||||
|
||||
The querying code would be:
|
||||
|
||||
[fields_timestamp_select]
|
||||
|
||||
If you don't set __time_zone__, you may apparently get the right results if you run
|
||||
both insertions and queries from clients that don't set `time_zone` and the server doesn't
|
||||
change its configuration. However, relying on this will make your applications brittle, so we
|
||||
don't recommend it.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section The TIME type]
|
||||
|
||||
The __TIME__ type is a signed duration with a resolution of one microsecond.
|
||||
It is represented using the [reflink time] type, an alias for a
|
||||
`std::chrono::duration` specialization with microseconds as period.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section Field accessors]
|
||||
|
||||
[link mysql.overview.fields.accessors This table] summarizes the
|
||||
available accesors for `field_view` and `field`.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section MySQL to C++ type mappings]
|
||||
[heading MySQL to C++ type mapping reference]
|
||||
|
||||
The following table reflects mapping from database types to C++ types.
|
||||
The range column shows the range of values that MySQL admits for that type. This library
|
||||
guarantees that any field retrieved from the database honors that range. The `column_type`
|
||||
column shows what [refmem metadata type] would return for a column of that type.
|
||||
|
||||
[table
|
||||
[table:dynamic_field_mappings
|
||||
[
|
||||
[MySQL type]
|
||||
[`field_kind`]
|
||||
@ -443,6 +484,8 @@ column shows what [refmem metadata type] would return for a column of that type.
|
||||
[
|
||||
A fixed precision numeric value. In this case, the string will contain
|
||||
the textual representation of the number (e.g. the string `"20.52"` for `20.52`).
|
||||
|
||||
This type is mapped to a string to avoid losing precision.
|
||||
]
|
||||
]
|
||||
[
|
||||
@ -488,81 +531,3 @@ column shows what [refmem metadata type] would return for a column of that type.
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[section:cpp_to_mysql C++ to MySQL type mapping reference]
|
||||
|
||||
This section shows how a parameter `v` in a expression `conn.execute(stmt.bind(v), result)`
|
||||
is interpreted by MySQL, depeding on `v`'s type:
|
||||
|
||||
[table
|
||||
[
|
||||
[C++ type]
|
||||
[MySQL type]
|
||||
[Compatible with...]
|
||||
]
|
||||
[
|
||||
[`signed char`, `short`, `int`, `long`, `long long`]
|
||||
[`BIGINT`]
|
||||
[Signed `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`]
|
||||
]
|
||||
[
|
||||
[`unsigned char`, `unsigned short`, `unsigned int`, `unsigned long`, `unsigned long long`]
|
||||
[`UNSIGNED BIGINT`]
|
||||
[Unsigned `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`, `YEAR`, `BIT`]
|
||||
]
|
||||
[
|
||||
[`std::string`, [reflink string_view], `std::string_view`, `const char*`]
|
||||
[`VARCHAR`]
|
||||
[`CHAR`, `VARCHAR`, `TEXT` (all sizes), `ENUM`, `SET`, `JSON`, `DECIMAL`, `NUMERIC`]
|
||||
]
|
||||
[
|
||||
[[reflink blob], [reflink blob_view]]
|
||||
[`BLOB`]
|
||||
[`BINARY`, `VARBINARY`, `BLOB` (all sizes), `GEOMETRY`]
|
||||
]
|
||||
[
|
||||
[`float`]
|
||||
[`FLOAT`]
|
||||
[`FLOAT`]
|
||||
]
|
||||
[
|
||||
[`double`]
|
||||
[`DOUBLE`]
|
||||
[`DOUBLE`]
|
||||
]
|
||||
[
|
||||
[[reflink date]]
|
||||
[`DATE`]
|
||||
[`DATE`]
|
||||
]
|
||||
[
|
||||
[[reflink datetime]]
|
||||
[`DATETIME`]
|
||||
[`DATETIME`, `TIMESTAMP`]
|
||||
]
|
||||
[
|
||||
[[reflink time]]
|
||||
[`TIME`]
|
||||
[`TIME`]
|
||||
]
|
||||
[
|
||||
[`std::nullptr_t`]
|
||||
[`NULL`]
|
||||
[Any of the other types. Used to insert `NULL`s, for example.]
|
||||
]
|
||||
[
|
||||
[[reflink field_view]]
|
||||
[Depends on the actual type stored by the field]
|
||||
[]
|
||||
]
|
||||
[
|
||||
[[reflink field]]
|
||||
[Depends on the actual type stored by the field]
|
||||
[]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
@ -1,94 +0,0 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:prepared_statements Prepared statements]
|
||||
|
||||
This section covers using [mysqllink sql-prepared-statements.html
|
||||
server-side prepared statements]. You should use them whenever a query
|
||||
contains parameters not known at compile-time.
|
||||
|
||||
[heading Preparing a statement]
|
||||
|
||||
To prepare a statement, call [refmem connection prepare_statement]
|
||||
or [refmem connection async_prepare_statement], passing your statement
|
||||
as a string. This yields a [reflink statement] object:
|
||||
|
||||
[prepared_statements_prepare]
|
||||
|
||||
The question mark characters (`?`) represent parameters
|
||||
(as described [mysqllink prepare.html here]).
|
||||
When you execute the statement (next section), you
|
||||
provide values for each of the parameters you declared, and the server
|
||||
will use these values to run the statement.
|
||||
|
||||
[heading Executing a statement]
|
||||
|
||||
To execute a statement, use any of the following functions:
|
||||
|
||||
* [refmem connection execute] or [refmem connection async_execute],
|
||||
which execute the statement and read the generated rows.
|
||||
* [refmem connection start_execution] and [refmem connection async_start_execution], which initiate a
|
||||
statement execution as a multi-function operation.
|
||||
|
||||
For example:
|
||||
|
||||
[prepared_statements_execute]
|
||||
|
||||
Some observations:
|
||||
|
||||
* You must pass in [*exactly as many parameters
|
||||
as the statement has]. Failing to do so will result in an error.
|
||||
* You don't need to sanitize the parameters anyhow. The server takes care of it.
|
||||
* Actual parameters are matched to `?` placeholders by order.
|
||||
* Parameters are passed as a `std::tuple`. You can pass in built-in integers,
|
||||
`float`, `double`, [reflink date], [reflink datetime], [reflink time],
|
||||
[reflink field_view] and [reflink field] objects as parameters.
|
||||
* `show_in_store` is passed as an `int` to `statement::bind()`, but is defined as a `TINYINT`
|
||||
(1 byte integer) in the table. As long as the passed integer is in range, MySQL
|
||||
will perform the required conversions. Otherwise, `execute()` will fail with an error
|
||||
(no undefined behavior is invoked).
|
||||
|
||||
You can also pass [reflink field_view]s and [reflink field]s as parameters. This is handy
|
||||
to insert `NULL` values:
|
||||
|
||||
[prepared_statements_execute_null]
|
||||
|
||||
For a full reference on the types you can pass as parameters when
|
||||
executing a statement, see [link mysql.fields.cpp_to_mysql this section].
|
||||
|
||||
[heading Executing a statement with a variable number of parameters]
|
||||
|
||||
The above approach works when you know at compile time how many parameters the statement has.
|
||||
In some scenarios (e.g. a graphical interface), this may not be the case. For these cases, you can
|
||||
`bind` a statement to a `field` or `field_view` iterator range:
|
||||
|
||||
[prepared_statements_execute_iterator_range]
|
||||
|
||||
[heading Closing a statement]
|
||||
|
||||
Prepared statements are created server-side, and thus consume server resources. If you don't need a
|
||||
[reflink statement] anymore, you can call [refmem connection close_statement] or
|
||||
[refmem connection async_close_statement] to instruct the server to deallocate it.
|
||||
|
||||
Prepared statements are managed by the server on a per-connection basis. Once you close your connection
|
||||
with the server, all prepared statements you have created using this connection will be automatically
|
||||
deallocated.
|
||||
|
||||
If you are creating your prepared statements at the beginning
|
||||
of your program and keeping them alive until the connection
|
||||
is closed, then there is no need to call `close_statement()`,
|
||||
as closing the connection will do the cleanup
|
||||
for you. If you are creating and destroying prepared statements
|
||||
dynamically, then it is advised to use `close_statement()` to prevent excessive
|
||||
resource usage in the server.
|
||||
|
||||
Finally, note that [reflink statement]'s destructor
|
||||
does not perform any server-side deallocation of the statement.
|
||||
This is because closing a statement involves a network
|
||||
operation that may block or fail.
|
||||
|
||||
[endsect]
|
257
doc/qbk/05_static_interface.qbk
Normal file
257
doc/qbk/05_static_interface.qbk
Normal file
@ -0,0 +1,257 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:static_interface The static interface]
|
||||
[nochunk]
|
||||
|
||||
To use the static interface, we must first define a data structure that describes the shape of
|
||||
your rows. We recommend using [@boost:/libs/describe/index.html Boost.Describe] for this.
|
||||
We define a plain `struct` with the fields returned by your our and annotate it with
|
||||
`BOOST_DESCRIBE_STRUCT` to enable reflection on it.
|
||||
|
||||
For example, given the following table definition and query:
|
||||
|
||||
[static_setup]
|
||||
|
||||
We can define our row type like this:
|
||||
|
||||
[describe_post]
|
||||
|
||||
To run the query, we can write:
|
||||
|
||||
[static_query]
|
||||
|
||||
Note that [refmem static_results rows] returns a `boost::span` object,
|
||||
which is a C++11 backport of `std::span`. The span points into memory owned by the
|
||||
`static_results` object. Care must be taken not to use this view object after the
|
||||
`static_results` goes out of scope.
|
||||
|
||||
[heading Field matching]
|
||||
|
||||
Columns in the query are matched to fields in the struct by name.
|
||||
If a struct field cannot be matched to any query column, an error is issued.
|
||||
Extra columns in the query are ignored.
|
||||
|
||||
If your query contains columns with names that don't qualify as C++ identifiers,
|
||||
you can use SQL aliases. For example, given this struct:
|
||||
|
||||
[describe_statistics]
|
||||
|
||||
You can write your query as:
|
||||
|
||||
[static_field_order]
|
||||
|
||||
[heading Using tuples]
|
||||
|
||||
You can also use `std::tuple`s as row types. This can be handy for simple queries:
|
||||
|
||||
[static_tuples]
|
||||
|
||||
Fields in tuples are matched to query columns by order. The query must return as many
|
||||
columns as fields the tuple has, at least. Any extra trailing columns in the query are ignored.
|
||||
|
||||
[heading Metadata checking]
|
||||
|
||||
The static interface will try to validate as soon as possible that the provided row type
|
||||
is compatible with the schema returned by the server. This process is known as [*metadata checking],
|
||||
and is performed before reading any data. The following checks are performed:
|
||||
|
||||
* [*Type compatibility]: the C++ type must be able to represent any value that the MySQL type can represent.
|
||||
For example, `std::int32_t` is compatible with `TINYINT` (1 byte integer), but not with `BIGINT` (8 byte integer).
|
||||
For a full list of allowable field types, [link mysql.static_interface.readable_field_reference refer to this table].
|
||||
* [*Nullability]: if MySQL reports that a column can be `NULL`, your type must account for it. You can use
|
||||
`std::optional<T>` or `boost::optional<T>` for these columns.
|
||||
|
||||
For example, if your table is defined like this:
|
||||
|
||||
[static_nulls_table]
|
||||
|
||||
Using the `post` type we defined above will cause an error, because
|
||||
the `body` field may be `NULL`, but our type doesn't account for it.
|
||||
In this case, the correct definition would be:
|
||||
|
||||
[describe_post_v2]
|
||||
|
||||
[heading Multi-resultset and multi-function operations]
|
||||
|
||||
You can use both with the dynamic interface. Please refer to the sections
|
||||
on [link mysql.multi_resultset multi-resultset operations] and
|
||||
[link mysql.multi_function multi-function operations] for more information.
|
||||
|
||||
[heading C++ standard requirements]
|
||||
|
||||
Using the static interface requires C++14. The `BOOST_MYSQL_CXX14` test macro
|
||||
is defined only if the static interface is supported. Including the static interface headers
|
||||
on an unsupported compiler doesn't cause any error, but classes like [reflink static_results]
|
||||
and [reflink static_execution_state] are not defined. The test macro is brought on scope by
|
||||
any of the static interface headers.
|
||||
|
||||
[heading Allowed field types]
|
||||
|
||||
All the types used within your Describe structs or tuples must be within
|
||||
the following table. A Describe struct or tuple composed of valid field
|
||||
types models the [reflink StaticRow] concept.
|
||||
|
||||
The following table is a reference of the C++ types that can be used in a
|
||||
`StaticRow` and their compatibility with MySQL database types:
|
||||
|
||||
[table:readable_field_reference
|
||||
[
|
||||
[C++ type]
|
||||
[Compatible with...]
|
||||
]
|
||||
[
|
||||
[`std::int8_t`]
|
||||
[
|
||||
__TINYINT__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::uint8_t`]
|
||||
[
|
||||
__TINYINT__ `UNSIGNED`
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::int16_t`]
|
||||
[
|
||||
__TINYINT__[br]
|
||||
__TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__ [br]
|
||||
__YEAR__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::uint16_t`]
|
||||
[
|
||||
__TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__ `UNSIGNED` [br]
|
||||
__YEAR__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::int32_t`]
|
||||
[
|
||||
__TINYINT__, __TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__, __SMALLINT__ `UNSIGNED`[br]
|
||||
__MEDIUMINT__, __MEDIUMINT__ `UNSIGNED`[br]
|
||||
__INT__[br]
|
||||
__YEAR__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::uint32_t`]
|
||||
[
|
||||
__TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__ `UNSIGNED`[br]
|
||||
__MEDIUMINT__ `UNSIGNED`[br]
|
||||
__INT__ `UNSIGNED`[br]
|
||||
__YEAR__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::int64_t`]
|
||||
[
|
||||
__TINYINT__, __TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__, __SMALLINT__ `UNSIGNED`[br]
|
||||
__MEDIUMINT__, __MEDIUMINT__ `UNSIGNED`[br]
|
||||
__INT__, __INT__ `UNSIGNED`[br]
|
||||
__BIGINT__[br]
|
||||
__YEAR__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`std::uint64_t`]
|
||||
[
|
||||
__TINYINT__ `UNSIGNED`[br]
|
||||
__SMALLINT__ `UNSIGNED`[br]
|
||||
__MEDIUMINT__ `UNSIGNED`[br]
|
||||
__INT__ `UNSIGNED`[br]
|
||||
__BIGINT__ `UNSIGNED`[br]
|
||||
__YEAR__[br]
|
||||
__BIT__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`bool`]
|
||||
[
|
||||
`BOOL` or `BOOLEAN` (alias for __TINYINT__).
|
||||
]
|
||||
]
|
||||
[
|
||||
[`float`]
|
||||
[
|
||||
__FLOAT__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`double`]
|
||||
[
|
||||
__FLOAT__, __DOUBLE__[br]
|
||||
]
|
||||
]
|
||||
[
|
||||
[`date`]
|
||||
[
|
||||
__DATE__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`datetime`]
|
||||
[
|
||||
__DATETIME__, __TIMESTAMP__
|
||||
]
|
||||
]
|
||||
[
|
||||
[`time`]
|
||||
[
|
||||
__TIME__
|
||||
]
|
||||
]
|
||||
[
|
||||
[
|
||||
`std::basic_string<char, std::char_traits<char>, Allocator>`[br][br]
|
||||
The object must be default-constructible.
|
||||
]
|
||||
[
|
||||
__CHAR__, __VARCHAR__, __TEXT__[br]
|
||||
__ENUM__, __SET__[br]
|
||||
__JSON__[br]
|
||||
__DECIMAL__/__NUMERIC__
|
||||
]
|
||||
]
|
||||
[
|
||||
[
|
||||
`std::basic_vector<unsigned char, Allocator>`[br][br]
|
||||
The object must be default-constructible.
|
||||
]
|
||||
[
|
||||
__BINARY__, __VARBINARY__, __BLOB__[br]
|
||||
__GEOMETRY__
|
||||
]
|
||||
]
|
||||
[
|
||||
[
|
||||
`std::optional<T>`[br][br]
|
||||
`T` must be any of the types listed in this table.
|
||||
]
|
||||
[
|
||||
Any type compatible with `T`
|
||||
]
|
||||
]
|
||||
[
|
||||
[
|
||||
`boost::optional<T>`[br][br]
|
||||
`T` must be any of the types listed in this table.
|
||||
]
|
||||
[
|
||||
Any type compatible with `T`
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
202
doc/qbk/07_prepared_statements.qbk
Normal file
202
doc/qbk/07_prepared_statements.qbk
Normal file
@ -0,0 +1,202 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:prepared_statements Prepared statements]
|
||||
|
||||
This section covers using [mysqllink sql-prepared-statements.html
|
||||
server-side prepared statements]. You should use them whenever a query
|
||||
contains parameters not known at compile-time.
|
||||
|
||||
[heading Preparing a statement]
|
||||
|
||||
To prepare a statement, call [refmem connection prepare_statement]
|
||||
or [refmem connection async_prepare_statement], passing your statement
|
||||
as a string. This yields a [reflink statement] object:
|
||||
|
||||
[prepared_statements_prepare]
|
||||
|
||||
The question mark characters (`?`) represent parameters
|
||||
(as described [mysqllink prepare.html here]).
|
||||
When you execute the statement (next section), you
|
||||
provide values for each of the parameters you declared, and the server
|
||||
will use these values to run the statement.
|
||||
|
||||
[heading Executing a statement]
|
||||
|
||||
To execute a statement, use any of the following functions:
|
||||
|
||||
* [refmem connection execute] or [refmem connection async_execute],
|
||||
which execute the statement and read the generated rows.
|
||||
* [refmem connection start_execution] and [refmem connection async_start_execution], which initiate a
|
||||
statement execution as a multi-function operation.
|
||||
|
||||
For example:
|
||||
|
||||
[prepared_statements_execute]
|
||||
|
||||
Some observations:
|
||||
|
||||
* You must pass in [*exactly as many parameters
|
||||
as the statement has]. Failing to do so will result in an error.
|
||||
* You don't need to sanitize the parameters anyhow. The server takes care of it.
|
||||
* Actual parameters are matched to `?` placeholders by order.
|
||||
* You can pass types like built-in integers, `float`, [reflink date] or `std::string`,
|
||||
with the expected effects. [link mysql.prepared_statements.writable_field_reference This table]
|
||||
contains a reference with all the allowed types.
|
||||
* You can also use the static interface to execute statements by replacing [reflink results]
|
||||
by [reflink static_results].
|
||||
|
||||
You can pass `std::optional` and `boost::optional` for parameters that may be `NULL`.
|
||||
If the optional doesn't have a value, `NULL` will be sent to the server. For example:
|
||||
|
||||
[prepared_statements_execute_null]
|
||||
|
||||
[heading Type casting with statement parameters]
|
||||
|
||||
MySQL is quite permissive with the type of statement parameters. In most cases,
|
||||
it will perform the required casts for you. For example, given this table definition:
|
||||
|
||||
[prepared_statements_casting_table]
|
||||
|
||||
We can write:
|
||||
|
||||
[prepared_statements_casting_execute]
|
||||
|
||||
MySQL expects a `TINYINT`, but we're sending an `int`, which is bigger.
|
||||
As long as the value is in range, this won't cause any trouble.
|
||||
If the value is out-of-range, `execute` will fail with an error.
|
||||
|
||||
[heading Executing a statement with a variable number of parameters]
|
||||
|
||||
The above approach works when you know at compile time how many parameters the statement has.
|
||||
In some scenarios (e.g. a graphical interface), this may not be the case. For these cases, you can
|
||||
`bind` a statement to a `field` or `field_view` iterator range:
|
||||
|
||||
[prepared_statements_execute_iterator_range]
|
||||
|
||||
[heading Closing a statement]
|
||||
|
||||
Prepared statements are created server-side, and thus consume server resources. If you don't need a
|
||||
[reflink statement] anymore, you can call [refmem connection close_statement] or
|
||||
[refmem connection async_close_statement] to instruct the server to deallocate it.
|
||||
|
||||
Prepared statements are managed by the server on a per-connection basis. Once you close your connection
|
||||
with the server, all prepared statements you have created using this connection will be automatically
|
||||
deallocated.
|
||||
|
||||
If you are creating your prepared statements at the beginning
|
||||
of your program and keeping them alive until the connection
|
||||
is closed, then there is no need to call `close_statement()`,
|
||||
as closing the connection will do the cleanup
|
||||
for you. If you are creating and destroying prepared statements
|
||||
dynamically, then it is advised to use `close_statement()` to prevent excessive
|
||||
resource usage in the server.
|
||||
|
||||
Finally, note that [reflink statement]'s destructor
|
||||
does not perform any server-side deallocation of the statement.
|
||||
This is because closing a statement involves a network
|
||||
operation that may block or fail.
|
||||
|
||||
|
||||
[heading Type mapping reference for prepared statement parameters]
|
||||
|
||||
The following table contains a reference of the types that can be used when binding a statement.
|
||||
If a type can be used this way, we say to satisfy the `WritableField` concept.
|
||||
The table shows how a parameter `v` in a expression `conn.execute(stmt.bind(v), result)`
|
||||
is interpreted by MySQL, depeding on `v`'s type.
|
||||
|
||||
|
||||
[table:writable_field_reference
|
||||
[
|
||||
[C++ type]
|
||||
[MySQL type]
|
||||
[Compatible with...]
|
||||
]
|
||||
[
|
||||
[`signed char`, `short`, `int`, `long`, `long long`]
|
||||
[`BIGINT`]
|
||||
[Signed `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`]
|
||||
]
|
||||
[
|
||||
[`unsigned char`, `unsigned short`, `unsigned int`, `unsigned long`, `unsigned long long`]
|
||||
[`UNSIGNED BIGINT`]
|
||||
[Unsigned `TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT`, `YEAR`, `BIT`]
|
||||
]
|
||||
[
|
||||
[`bool`]
|
||||
[`BIGINT` (`1` if `true`, `0` if `false`)]
|
||||
[`TINYINT`]
|
||||
]
|
||||
[
|
||||
[`std::basic_string<char, std::char_traits<char>, Allocator>` (including `std::string`), [reflink string_view], `std::string_view`, `const char*`]
|
||||
[`VARCHAR`]
|
||||
[`CHAR`, `VARCHAR`, `TEXT` (all sizes), `ENUM`, `SET`, `JSON`, `DECIMAL`, `NUMERIC`]
|
||||
]
|
||||
[
|
||||
[`std::basic_vector<unsigned char, Allocator>` (including [reflink blob]), [reflink blob_view]]
|
||||
[`BLOB`]
|
||||
[`BINARY`, `VARBINARY`, `BLOB` (all sizes), `GEOMETRY`]
|
||||
]
|
||||
[
|
||||
[`float`]
|
||||
[`FLOAT`]
|
||||
[`FLOAT`]
|
||||
]
|
||||
[
|
||||
[`double`]
|
||||
[`DOUBLE`]
|
||||
[`DOUBLE`]
|
||||
]
|
||||
[
|
||||
[[reflink date]]
|
||||
[`DATE`]
|
||||
[`DATE`]
|
||||
]
|
||||
[
|
||||
[[reflink datetime]]
|
||||
[`DATETIME`]
|
||||
[`DATETIME`, `TIMESTAMP`]
|
||||
]
|
||||
[
|
||||
[[reflink time]]
|
||||
[`TIME`]
|
||||
[`TIME`]
|
||||
]
|
||||
[
|
||||
[`std::nullptr_t`]
|
||||
[`NULL`]
|
||||
[Any of the other types. Used to insert `NULL`s, for example.]
|
||||
]
|
||||
[
|
||||
[`std::optional<T>`]
|
||||
[
|
||||
Applies `T`'s type mapping if the optional has a value.[br]
|
||||
`NULL` otherwise
|
||||
]
|
||||
[]
|
||||
]
|
||||
[
|
||||
[`boost::optional<T>`]
|
||||
[
|
||||
Applies `T`'s type mapping if the optional has a value.[br]
|
||||
`NULL` otherwise
|
||||
]
|
||||
[]
|
||||
]
|
||||
[
|
||||
[[reflink field_view]]
|
||||
[Depends on the actual type stored by the field]
|
||||
[]
|
||||
]
|
||||
[
|
||||
[[reflink field]]
|
||||
[Depends on the actual type stored by the field]
|
||||
[]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
@ -15,21 +15,52 @@ to other SQL statements. Contrary to other statements, `CALL` may generate more
|
||||
|
||||
For example, given a stored procedure like this:
|
||||
|
||||
[multi_resultset_procedure]
|
||||
__sp_get_employees__
|
||||
|
||||
You can call it using a prepared statement, with the usual syntax:
|
||||
A statement like `CALL get_employees('my_company')` will generate three resultsets:
|
||||
|
||||
[multi_resultset_call]
|
||||
* A first resultset containing the company matched by the first `SELECT`.
|
||||
* A second resultset containing the employees matched by the second `SELECT`.
|
||||
* A third, empty resultset containing information about the `CALL` statement.
|
||||
|
||||
MySQL responds here with two resultsets:
|
||||
Every resultset contains its own rows, metadata, last insert ID, affected rows and so on.
|
||||
|
||||
* A first resultset containing the employee matched by the `SELECT` query in the procedure.
|
||||
* A second, empty resultset containing information about the `CALL` statement.
|
||||
|
||||
The [reflink results] class can store any number of resultsets.
|
||||
It can be understood as a random-access collection of resultsets. To obtain the first resultset, we can write:
|
||||
[heading Calling procedures using the dynamic interface]
|
||||
|
||||
[multi_resultset_first_resultset]
|
||||
The same [reflink results] class we've been using supports storing more than one resultset.
|
||||
You can execute a `CALL` statement as any other SQL statement:
|
||||
|
||||
[multi_resultset_call_dynamic]
|
||||
|
||||
In this context, `results` can be seen as a random-access collection of resultsets. You can access
|
||||
resultsets by index using [refmem results at] and [reflink2 results.operator_lb__rb_ results::operator[]].
|
||||
These operations yield a [reflink resultset_view], which is a lightweight object pointing into memory owned by the `results`
|
||||
object. You can take ownserhip of a `resultset_view` using the [reflink resultset] class. For example:
|
||||
|
||||
[multi_resultset_results_as_collection]
|
||||
|
||||
[heading Calling procedures using the static interface]
|
||||
|
||||
The [reflink static_results] class supports operations that return multiple
|
||||
resultsets, too. As with other SQL statements, we need to define the row types
|
||||
in our resultsets in advance:
|
||||
|
||||
[describe_stored_procedures]
|
||||
|
||||
We can now use `static_results`, passing it as many template arguments as
|
||||
resultsets we expect. The library will check that the correct number of
|
||||
resultsets are actually returned by the server, and will parse them into
|
||||
the row types that we provided:
|
||||
|
||||
[multi_resultset_call_static]
|
||||
|
||||
|
||||
Use [refmem static_results rows] with an explicit index to access each resultset's data.
|
||||
You can also use explicit indices with the other accessor functions,
|
||||
like [refmem static_results meta] and [refmem static_results last_insert_id].
|
||||
|
||||
For more information about the static interface, please refer to [link mysql.static_interface this section].
|
||||
|
||||
[heading Determining the number of resultsets]
|
||||
|
||||
@ -74,16 +105,24 @@ You can get the value of `OUT` and `INOUT` parameters in stored procedures by us
|
||||
prepared statement placeholders for them. When doing this, you will receive another resultset
|
||||
with a single row containing all output parameter values. This resultset is located after
|
||||
all resultsets generated by `SELECT`s, and before the final, empty resultset.
|
||||
To simplify things, you can use [refmem results out_params] to retrieve them:
|
||||
|
||||
For example, given this procedure:
|
||||
|
||||
__sp_create_employee__
|
||||
|
||||
You can use:
|
||||
|
||||
[multi_resultset_out_params]
|
||||
|
||||
[refmem results out_params] simplifies the process.
|
||||
|
||||
[warning
|
||||
Due to a bug in MySQL, some `OUT` parameters are sent with wrong types.
|
||||
Concretely, string parameters are always sent as blobs, so you will have to
|
||||
use [refmem field_view as_blob] instead of [refmem field_view as_string].
|
||||
]
|
||||
|
||||
|
||||
[heading:multi_queries Semicolon-separated queries]
|
||||
|
||||
It is possible to run several semicolon-separated text queries in a single [refmem connection execute] call.
|
||||
@ -92,13 +131,9 @@ before connecting:
|
||||
|
||||
[multi_resultset_multi_queries]
|
||||
|
||||
You can access the resultsets produced by individual queries using [refmem results at] and [reflink2 results.operator_lb__rb_ results::operator[]].
|
||||
These operations yield a [reflink resultset_view], which is a lighteight object pointing into memory owned by the `results`
|
||||
object. You can take ownserhip of a `resultset_view` using the [reflink resultset] class. For example:
|
||||
|
||||
[multi_resultset_results_as_collection]
|
||||
|
||||
Note that statements like `DELIMITER` [*do not work] using this feature. This is because
|
||||
`DELIMITER` is a pseudo-command for the `mysql` command line tool, not actual SQL.
|
||||
|
||||
You can also use the static interface with multi-queries. It works the same as with stored procedures.
|
||||
|
||||
[endsect]
|
@ -11,7 +11,9 @@
|
||||
Multi-function operations allow running operations as a set of separate
|
||||
steps, which gives you better control over execution. They work by splitting
|
||||
some of the reads and writes into several function calls.
|
||||
You can use multi-function operations to execute text queries and prepared statements.
|
||||
|
||||
You can use multi-function operations to execute text queries and prepared statements,
|
||||
and through the dynamic or the static interface.
|
||||
|
||||
[heading Protocol primer]
|
||||
|
||||
@ -33,7 +35,7 @@ There are two separate cases:
|
||||
* If your query retrieved at least one column (even if no rows were generated), we're in ['case 1]. The server sends:
|
||||
* An initial packet informing that the query executed correctly, and that we're in ['case 1].
|
||||
* Some matadata packets describing the columns that the query retrieved. These become available
|
||||
under [refmem results meta] and [refmem execution_state meta], and are necessary to parse the rows.
|
||||
to the user and are necessary to parse the rows.
|
||||
* The actual rows.
|
||||
* An OK packet, which marks the end of the resultset and contains information like `last_insert_id` and
|
||||
`affected_rows`.
|
||||
@ -54,33 +56,28 @@ Some takeaways:
|
||||
subsequent operations will mistakenly read them as their response, causing packet mismatches.
|
||||
Be careful and don't let this happen!
|
||||
|
||||
[heading Starting a multi-function operation]
|
||||
|
||||
Given the following setup:
|
||||
|
||||
[heading Using multi-function operations through the dynamic interface]
|
||||
|
||||
[reflink execution_state] is the main class for the dynamic interface in multi-function
|
||||
operations. An execution state holds information required to progress the execution operation,
|
||||
like metadata (required to parse the rows) and protocol state. Contrary to `results`, it doesn't contain
|
||||
the rows.
|
||||
|
||||
Given the following table definition:
|
||||
|
||||
[multi_function_setup]
|
||||
|
||||
You can start a multi-function operation using [refmem connection start_execution]:
|
||||
|
||||
[table
|
||||
[
|
||||
[Text queries]
|
||||
[Prepared statements]
|
||||
]
|
||||
[
|
||||
[[multi_function_text_queries]]
|
||||
[[multi_function_statements]]
|
||||
]
|
||||
]
|
||||
[multi_function_dynamic_start]
|
||||
|
||||
[heading Reading rows]
|
||||
We now [*must] read all the generated rows
|
||||
by calling [refmem connection read_some_rows], which will return a batch of an unspecified size:
|
||||
|
||||
Once the operation has been started, you [*must] read all the generated rows
|
||||
by calling [refmem connection read_some_rows], which will return a batch of an unspecified size.
|
||||
|
||||
This is the typical use of `read_some_rows`:
|
||||
|
||||
[multi_function_read_some_rows]
|
||||
[multi_function_dynamic_read]
|
||||
|
||||
Some remarks:
|
||||
|
||||
@ -95,15 +92,48 @@ This view is valid until the connection performs any other operation involving a
|
||||
Note that there is no need to distinguish between ['case 1] and ['case 2] in the diagram above in our code,
|
||||
as reading rows for a complete operation is well defined.
|
||||
|
||||
|
||||
[heading Using multi-function operations through the static interface]
|
||||
|
||||
The mechanics are similar to what's been exposed above. The static interface uses
|
||||
[reflink static_execution_state] to carry state. As with [reflink static_results], we must define and
|
||||
pass a type describing our rows:
|
||||
|
||||
[describe_post]
|
||||
|
||||
We can now start our operation using the same [refmem connection start_execution]:
|
||||
|
||||
[multi_function_static_start]
|
||||
|
||||
We now [*must] read all the generated rows by calling [refmem connection read_some_rows]:
|
||||
|
||||
[multi_function_static_read]
|
||||
|
||||
Some remarks:
|
||||
|
||||
* [reflink static_execution_state] doesn't store rows anyhow. It uses the row types passed
|
||||
as template parameters to validate the metadata returned by the server, and ensure it is compatible
|
||||
with the C++ data structures that will be used with `read_some_rows`.
|
||||
* We must pass `read_some_rows` a `boost::span` of the appropriate row type.
|
||||
We've used `std::array` to place rows on the stack, but you can use any other contiguous range.
|
||||
* `read_some_rows` returns the number of read rows. At maximum, this will be the size
|
||||
of the span, but there may be less, depending on row and network buffer sizes.
|
||||
* If there are rows to be read, `read_some_rows` will return at least one, but may return more.
|
||||
* [refmem execution_state complete] returns `true` after we've read the final OK packet for this operation.
|
||||
* The final read may or may not return rows, depending on the number of rows and their size.
|
||||
* Calling `read_some_rows` after reading the final OK packet always reads zero rows.
|
||||
|
||||
|
||||
|
||||
[heading Accessing metadata and OK packet data]
|
||||
|
||||
You can access metadata at any point, using [refmem execution_state meta]. This function returns a collection of [reflink metadata]
|
||||
objects. There is one object for each column retrieved by the SQL query, and in the same order as in the query. You can find a bunch
|
||||
of useful information in this object, like the column name, its type, whether it's a key or not, and so on.
|
||||
You can access metadata at any point, using [refmem execution_state meta] or [refmem static_execution_state meta].
|
||||
This function returns a collection of [reflink metadata] objects. For more information, plase refer to [link mysql.meta this section].
|
||||
|
||||
You can access OK packet data using functions like [refmem execution_state last_insert_id]
|
||||
and [refmem execution_state affected_rows]. As this information is contained in the OK packet,
|
||||
[*it can only be accessed once [refmem execution_state complete] returns `true`].
|
||||
You can access OK packet data using functions like [refmemunq execution_state last_insert_id]
|
||||
and [refmemunq execution_state affected_rows] in both `execution_state` and `static_execution_state`.
|
||||
As this information is contained in the OK packet,
|
||||
[*it can only be accessed once the [refmemunq execution_state complete] function returns `true`].
|
||||
|
||||
[heading Using multi-function operations with stored procedures and multi-queries]
|
||||
|
||||
@ -120,29 +150,57 @@ The message exchange is as follows:
|
||||
* Another resultset is sent, with the same structure as the previous one. The process is
|
||||
repeated until an OK packet indicates that no more resultsets follow.
|
||||
|
||||
This can be translated into the following code:
|
||||
For example, given the following stored procedure:
|
||||
|
||||
[multi_function_stored_procedure]
|
||||
[/ This is an actual procedure. Code imports from SQL don't work. Make sure it doesn't go out of sync ]
|
||||
[!teletype]
|
||||
```
|
||||
CREATE PROCEDURE get_company(IN pin_company_id CHAR(10))
|
||||
BEGIN
|
||||
START TRANSACTION READ ONLY;
|
||||
SELECT id, name, tax_id FROM company WHERE id = pin_company_id;
|
||||
SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id;
|
||||
COMMIT;
|
||||
END
|
||||
```
|
||||
|
||||
Note that we're using [refmem execution_state should_read_rows] instead of [refmem execution_state complete]
|
||||
We can write:
|
||||
|
||||
[table
|
||||
[
|
||||
[Dynamic interface]
|
||||
[Static interface]
|
||||
]
|
||||
[
|
||||
[[multi_function_stored_procedure_dynamic]]
|
||||
[[multi_function_stored_procedure_static]]
|
||||
]
|
||||
]
|
||||
|
||||
Note that we're using [refmemunq execution_state should_read_rows] instead of [refmemunq execution_state complete]
|
||||
as our loop termination condition. `complete()` returns true when all the resultsets have been read,
|
||||
while `should_read_rows()` will return false once an individual result has been fully read.
|
||||
|
||||
When using the static interface with multi-function operations, not all schema mismatches can be found
|
||||
by the `start_execution` function, since not all the information is available at this point. Errors may
|
||||
be reported by `read_some_rows` and `read_resultset_head`, too. Overall, the same checks are performed as
|
||||
when using [refmem connection execute], but at different points in time.
|
||||
|
||||
[heading Multi-resultset in the general case]
|
||||
|
||||
`execution_state` can be seen as a state machine with four states. Each state
|
||||
`execution_state` and `static_execution_state` can be seen as state machines with four states. Each state
|
||||
describes which reading function should be invoked next:
|
||||
|
||||
* [refmem execution_state should_start_op]: the initial state, after you default-construct an `execution_state`.
|
||||
* [refmemunq execution_state should_start_op]: the initial state, after you default-construct an `execution_state`.
|
||||
You should call [refmem connection start_execution] or [refmem connection async_start_execution] to start the operation.
|
||||
* [refmem execution_state should_read_rows]: the next operation should be [refmem connection read_some_rows],
|
||||
* [refmemunq execution_state should_read_rows]: the next operation should be [refmem connection read_some_rows],
|
||||
to read the generated rows.
|
||||
* [refmem execution_state should_read_head]: the next operation should be [refmem connection read_resultset_head],
|
||||
* [refmemunq execution_state should_read_head]: the next operation should be [refmem connection read_resultset_head],
|
||||
to read the next resultset metadata. Only operations that generate multiple resultsets go into this state.
|
||||
* [refmem execution_state complete]: no more operations are required.
|
||||
* [refmemunq execution_state complete]: no more operations are required.
|
||||
|
||||
For multi-function operations, you may also access OK packet data ever time a resultset has completely
|
||||
been read, i.e. when [refmem execution_state should_read_head] returns `true`.
|
||||
been read, i.e. when [refmemunq execution_state should_read_head] returns `true`.
|
||||
|
||||
[link mysql.examples.source_script This example] shows this general case. It uses
|
||||
multi-queries to run an arbitrary SQL script file.
|
||||
@ -160,7 +218,8 @@ performing a single `read_some` operation on the stream (or using cached data).
|
||||
If there are rows to read, `read_some_rows` guarantees to read at least one. This means that,
|
||||
if doing what we described yields no rows (e.g. because of a large row that doesn't fit
|
||||
into the read buffer), `read_some_rows` will grow the buffer or perform more reads until at least
|
||||
one row has been read.
|
||||
one row has been read. If you're using the static interface, the number of read rows is limited
|
||||
by the size of span you passed, too.
|
||||
|
||||
If you want to get the most of `read_some_rows`, customize the initial read buffer size
|
||||
to maximize the number of rows that each batch retrieves.
|
@ -30,15 +30,14 @@ All asynchronous functions are overloaded
|
||||
to accept an optional [reflink diagnostics] output parameter. It is populated
|
||||
with any server-provided error information before calling the completion handler.
|
||||
|
||||
[section:single_outstanding_op Single outstanding operation per connection]
|
||||
[heading Single outstanding operation per connection]
|
||||
|
||||
As mentioned in [link mysql.overview.async this section], only a single async
|
||||
operation per connection can be outstanding at a given point in time.
|
||||
If you need to perform queries in parallel, open more connections to the server.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:completion_tokens Completion tokens]
|
||||
[heading Completion tokens]
|
||||
|
||||
Any completion token you may use with Boost.Asio can also be used
|
||||
with this library. Here are some of the most common:
|
||||
@ -108,9 +107,8 @@ with this library. Here are some of the most common:
|
||||
We have listed the most common ones here, but you can craft your own
|
||||
and use it with this library's async operations.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:default_completion_tokens Default completion tokens]
|
||||
[heading Default completion tokens]
|
||||
|
||||
__Self__ also supports default completion tokens. Recall that
|
||||
some stream types may have an associated __Executor__ that
|
||||
@ -122,9 +120,8 @@ and the default will be used.
|
||||
[link mysql.examples.default_completion_tokens This example]
|
||||
demonstrates using default completion tokens with __Self__.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:cancellations_and_timeouts Cancellations and timeouts]
|
||||
[heading Cancellations and timeouts]
|
||||
|
||||
All async operations in this library support
|
||||
[@boost:/doc/html/boost_asio/overview/core/cancellation.html per-operation cancellation].
|
||||
@ -145,5 +142,3 @@ operation is cancelled, the connection is left in an unspecified state, and
|
||||
you should close or destroy it.
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect] [/ async]
|
@ -13,7 +13,7 @@ make use of TLS connections, as TLS is required for the
|
||||
[mysqllink caching-sha2-pluggable-authentication.html `caching_sha2_password`]
|
||||
authentication plugin, which is the default in MySQL 8.0.
|
||||
|
||||
[section:streams SSL-enabled streams]
|
||||
[heading SSL-enabled streams]
|
||||
|
||||
To use SSL/TLS, you must use a [reflink connection] with a
|
||||
[reflink Stream] that supports SSL. A SSL-enabled stream must inherit from
|
||||
@ -26,9 +26,9 @@ Note that there is no need to use TLS when using UNIX sockets. As the traffic do
|
||||
leave the machine, MySQL considers them secure, and will allow using authentication
|
||||
plugins like `caching_sha2_password` even if TLS is not used.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:handshake When is the SSL handshake performed?]
|
||||
|
||||
[heading When is the SSL handshake performed?]
|
||||
|
||||
The SSL handshake is performed while establishing the connection to the MySQL server,
|
||||
as part of the [refmem connection handshake] and [refmem connection async_handshake]. The functions
|
||||
@ -59,9 +59,8 @@ The functions [refmem connection close] and [refmem connection async_close]
|
||||
are implemented in terms of [refmem connection quit] and
|
||||
[refmem connection async_quit], and thus also perform the TLS shutdown.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:negotiation SSL negotiation]
|
||||
[heading:negotiation SSL negotiation]
|
||||
|
||||
During the handshake, client and server will negotiate whether to use TLS or not. For SSL
|
||||
capable streams, we support using TLS conditionally. This is controlled using the [reflink ssl_mode]
|
||||
@ -88,6 +87,5 @@ be used.
|
||||
|
||||
See [link mysql.connparams this section] for more information on [reflink handshake_params].
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
@ -12,7 +12,7 @@ This section discusses several aspects regarding the establishment
|
||||
of a connection with the MySQL server, including a detailed
|
||||
description of the parameters in [reflink handshake_params].
|
||||
|
||||
[section:auth Authentication]
|
||||
[heading Authentication]
|
||||
|
||||
The parameters [refmem handshake_params username] and
|
||||
[refmem handshake_params password] are mandatory. The
|
||||
@ -57,9 +57,7 @@ an unsupported authentication plugin, the operation will fail.
|
||||
may be lifted in the future.
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:database Connect with database]
|
||||
[heading Connect with database]
|
||||
|
||||
The parameter [refmem handshake_params database] is a string
|
||||
with the database name to connect to. If you specify it,
|
||||
@ -69,9 +67,7 @@ to select no database. You can always employ a __USE__
|
||||
statement to select a different database after establishing
|
||||
the connection.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:collation Connection encoding and collation]
|
||||
[heading Connection encoding and collation]
|
||||
|
||||
When establishing a connection, you specify a numeric collation ID
|
||||
parameter ([refmem handshake_params connection_collation]), which will
|
||||
@ -96,9 +92,7 @@ about character sets.
|
||||
and collation. If you want to be sure, use a `"SET NAMES"` statement.
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:ssl SSL/TLS]
|
||||
[heading SSL/TLS]
|
||||
|
||||
When establising a connection, you can specify a [reflink ssl_mode]
|
||||
value to configure whether to use SSL/TLS or not. As explained in
|
||||
@ -106,7 +100,5 @@ value to configure whether to use SSL/TLS or not. As explained in
|
||||
employed to configure SSL negotiation. This value is ignored if the
|
||||
underlying stream does not support SSL.
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[endsect] [/ connparams]
|
@ -9,7 +9,7 @@
|
||||
[section:charsets Character sets]
|
||||
[nochunk]
|
||||
|
||||
[section Character set refresher]
|
||||
[heading Character set refresher]
|
||||
|
||||
MySQL defines a character set as "a set of symbols and their respective encodings". `ascii`,
|
||||
`latin1`, `utf8` and `utf16` are character sets supported by MySQL.
|
||||
@ -22,9 +22,7 @@ For example, `latin1_swedish_ci` is the default collation for the `latin1` chara
|
||||
|
||||
You can find more information about these concepts in [mysqllink charset.html the official MySQL docs on character sets].
|
||||
|
||||
[endsect]
|
||||
|
||||
[section The connection character set and collation]
|
||||
[heading The connection character set and collation]
|
||||
|
||||
Every connection has an associated character set and collation. The connection's character set determines
|
||||
the encoding for character strings sent to and retrieved from the server. This includes SQL query strings,
|
||||
@ -50,9 +48,7 @@ character set and collation. You can specify this in two ways:
|
||||
|
||||
[charsets_set_names]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section character_set_results and character_set_client]
|
||||
[heading character_set_results and character_set_client]
|
||||
|
||||
Both of the above methods are shortcuts to set several session-level variables.
|
||||
The ones that impact this library's behavior are:
|
||||
@ -80,7 +76,7 @@ The ones that impact this library's behavior are:
|
||||
|
||||
The table below summarizes the encoding used by each piece of functionality in this library:
|
||||
|
||||
[table
|
||||
[table:string_encoding
|
||||
[
|
||||
[Functionality]
|
||||
[Encoding given by...]
|
||||
@ -130,5 +126,3 @@ The table below summarizes the encoding used by each piece of functionality in t
|
||||
]
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
104
doc/qbk/18_time_types.qbk
Normal file
104
doc/qbk/18_time_types.qbk
Normal file
@ -0,0 +1,104 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:time_types Time types: date, datetime and time]
|
||||
[nochunk]
|
||||
|
||||
The [reflink date], [reflink datetime] and [reflink time] provide
|
||||
support for MySQL's native date and time types. This section expands
|
||||
on how to use them.
|
||||
|
||||
[heading The date type]
|
||||
|
||||
[reflink date] represents MySQL __DATE__ in C++. `date` holds the year, month and day components of a date.
|
||||
It is a type close to the protocol, rather than a vocabulary type. The main reason for using `date` instead
|
||||
of a `std::chrono::time_point` type is that, under certain configurations, MySQL allows storing invalid
|
||||
dates, such as `2020-00-01`. These are not representable as a `std::chrono::time_point`.
|
||||
|
||||
Unless dealing with these special values, we recommend converting `date`s to a `time_point` before using them.
|
||||
The member type [refmem date time_point] is a system-clock `time_point` that can represent the entire
|
||||
MySQL `DATE` range without problems. You can use [refmem date as_time_point] to perform the cast:
|
||||
|
||||
[time_types_date_as_time_point]
|
||||
|
||||
If the date is not valid, `as_time_point` will throw an exception.
|
||||
|
||||
You can query whether a `date` contains a valid date or not using [refmem date valid]:
|
||||
|
||||
[time_types_date_valid]
|
||||
|
||||
You can combine it with [refmem date get_time_point], which performs an unchecked
|
||||
conversion:
|
||||
|
||||
[time_types_date_get_time_point]
|
||||
|
||||
You can also construct a `date` from a `time_point`. If the `time_point` is
|
||||
out of range, an exception is thrown.
|
||||
|
||||
|
||||
|
||||
[heading The datetime type]
|
||||
|
||||
[reflink datetime] represents MySQL __DATETIME__ and __TIMESTAMP__ in C++. `datetime` represents a broken time point,
|
||||
having year, month, day, hour, minute, second and microseconds.
|
||||
|
||||
MySQL also accepts invalid datetimes (like `2020-00-10 10:20:59.000000`). The same considerations for
|
||||
`date` apply:
|
||||
|
||||
[time_types_datetime]
|
||||
|
||||
The `datetime` object doesn't carry any time zone information with it. The time zone semantics
|
||||
depend on the actual MySQL type:
|
||||
|
||||
* __DATETIME__ is a "naive" time point object. It represents a time point without any time zone
|
||||
information at all. It is up to the user to interpret which time zone the object is in.
|
||||
* When a __TIMESTAMP__ object is inserted, it is interpreted to be in the connection's local time zone,
|
||||
as given by the __time_zone__ variable, and converted to UTC for storage. When retrieved, it is converted back
|
||||
to the time zone indicated by __time_zone__. The retrieved value of a `TIMESTAMP`
|
||||
field is thus a time point in some local time zone, dictated by the current
|
||||
__time_zone__ variable. As this variable can be changed programmatically from SQL, without
|
||||
the library knowing it, we represent `TIMESTAMP`'s using the `datetime` object, which doesn't include time zone information.
|
||||
|
||||
When using `TIMESTAMP`, we recommend setting the __time_zone__ to a known value. To illustrate this,
|
||||
consider an event-logging system with the following table definition:
|
||||
|
||||
[time_types_timestamp_setup]
|
||||
|
||||
We will be inserting events with an explicit timestamp. We may also want to retrieve events with
|
||||
a timestamp filter. This is what our prepared statements would look like:
|
||||
|
||||
[time_types_timestamp_stmts]
|
||||
|
||||
These statements may be run from different parts of our code, or even from different applications.
|
||||
To get consistent results, we must make sure that the time zones used during insertion and retrieval are the same.
|
||||
By default, __time_zone__ gets set to `SYSTEM`, which will use the server's time zone settings.
|
||||
This is not what we want here, so let's change it:
|
||||
|
||||
[time_types_timestamp_set_time_zone]
|
||||
|
||||
With this, the insertion code can look like:
|
||||
|
||||
[time_types_timestamp_insert]
|
||||
|
||||
The querying code would be:
|
||||
|
||||
[time_types_timestamp_select]
|
||||
|
||||
If you don't set __time_zone__, you may apparently get the right results if you run
|
||||
both insertions and queries from clients that don't set `time_zone` and the server doesn't
|
||||
change its configuration. However, relying on this will make your applications brittle, so we
|
||||
don't recommend it.
|
||||
|
||||
|
||||
[heading The TIME type]
|
||||
|
||||
The __TIME__ type is a signed duration with a resolution of one microsecond.
|
||||
It is represented using the [reflink time] type, an alias for a
|
||||
`std::chrono::duration` specialization with microseconds as period.
|
||||
|
||||
|
||||
[endsect]
|
@ -13,21 +13,22 @@ setup] first.
|
||||
|
||||
Here is a list of available examples:
|
||||
|
||||
# [link mysql.examples.text_queries Text queries]
|
||||
# [link mysql.examples.prepared_statements Prepared statements]
|
||||
# [link mysql.examples.metadata Metadata]
|
||||
# [link mysql.examples.unix_socket UNIX sockets]
|
||||
# [link mysql.examples.async_callbacks Async functions using callbacks]
|
||||
# [link mysql.examples.async_futures Async functions using futures]
|
||||
# [link mysql.examples.async_coroutines Async functions using stackful coroutines]
|
||||
# [link mysql.examples.async_coroutinescpp20 Async functions using C++20 coroutines]
|
||||
# [link mysql.examples.default_completion_tokens Async functions using default completion tokens]
|
||||
# [link mysql.examples.timeouts Setting timeouts]
|
||||
# [link mysql.examples.prepared_statements_cpp11 Using prepared statements with the dynamic interface (C++11)]
|
||||
# [link mysql.examples.prepared_statements_cpp14 Using prepared statements with the static interface (C++14)]
|
||||
# [link mysql.examples.stored_procedures_cpp11 Using stored procedures with the dynamic interface (C++11)]
|
||||
# [link mysql.examples.stored_procedures_cpp14 Using stored procedures with the static interface (C++14)]
|
||||
# [link mysql.examples.metadata Metadata]
|
||||
# [link mysql.examples.unix_socket UNIX sockets]
|
||||
# [link mysql.examples.ssl Setting SSL options]
|
||||
# [link mysql.examples.stored_procedures Using stored procedures]
|
||||
# [link mysql.examples.source_script Using multi-queries to source a .sql file]
|
||||
|
||||
[section:setup Setup]
|
||||
[heading Setup]
|
||||
|
||||
To run the examples, you need a MySQL server you can connect to.
|
||||
Examples make use of a database named `boost_mysql_examples`.
|
||||
@ -56,32 +57,186 @@ and is not suitable for production.
|
||||
|
||||
The root MySQL user for these containers is `root` and it has an empty password.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:text_queries Text queries]
|
||||
[section:async_callbacks Async functions using callbacks]
|
||||
|
||||
This example demonstrates how to issue text queries, without user-supplied parameters.
|
||||
It employs synchronous functions with exceptions as error handling. __see_error_handling__
|
||||
This example demonstrates how use the asynchronous functions using callbacks.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/text_queries.cpp]
|
||||
[example_text_queries]
|
||||
[import ../../example/async_callbacks.cpp]
|
||||
[example_async_callbacks]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:prepared_statements Prepared statements]
|
||||
|
||||
This example demonstrates how to use prepared statements.
|
||||
It employs synchronous functions with exceptions as error handling. __see_error_handling__
|
||||
|
||||
|
||||
[section:async_futures Async functions using futures]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using futures.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/prepared_statements.cpp]
|
||||
[example_prepared_statements]
|
||||
[import ../../example/async_futures.cpp]
|
||||
[example_async_futures]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:async_coroutines Async functions using stackful coroutines]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using stackful coroutines
|
||||
(using [asioreflink yield_context yield_context] and
|
||||
[asioreflink spawn spawn]).
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_coroutines.cpp]
|
||||
[example_async_coroutines]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:async_coroutinescpp20 Async functions using C++20 coroutines]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using C++20 coroutines
|
||||
(using [asioreflink use_awaitable use_awaitable] and [asioreflink
|
||||
co_spawn co_spawn]).
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_coroutinescpp20.cpp]
|
||||
[example_async_coroutinescpp20]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:default_completion_tokens Async functions using default completion tokens]
|
||||
|
||||
This example demonstrates how to use Boost.Asio's
|
||||
default completion token functionality with __Self__.
|
||||
For that purpose, it employs C++20 coroutines.
|
||||
If you are not familiar with them, look at
|
||||
[link mysql.examples.async_coroutinescpp20 this example]
|
||||
first.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/default_completion_tokens.cpp]
|
||||
[example_default_completion_tokens]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:timeouts Timeouts]
|
||||
|
||||
This example demonstrates how to use Boost.Asio's
|
||||
cancellation features to add timeouts to your async operations,
|
||||
including the ones provided by __Self__.
|
||||
For that purpose, it employs C++20 coroutines.
|
||||
If you are not familiar with them, look at
|
||||
[link mysql.examples.async_coroutinescpp20 this example]
|
||||
first.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/timeouts.cpp]
|
||||
[example_timeouts]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:prepared_statements_cpp11 Using prepared statements with the dynamic interface (C++11)]
|
||||
|
||||
This example demonstrates how to use prepared statements with the dynamic interface to
|
||||
implement a minimal order management system for an online store.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
This examples requires you to run [link_to_file example/order_management/db_setup.sql].
|
||||
You can find table definitions there.
|
||||
|
||||
[import ../../example/order_management/prepared_statements_cpp11.cpp]
|
||||
|
||||
[example_prepared_statements_cpp11]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:prepared_statements_cpp14 Using prepared statements with the static interface (C++14)]
|
||||
|
||||
This example demonstrates how to use prepared statements with the static interface to
|
||||
implement a minimal order management system for an online store.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
This examples requires you to run [link_to_file example/order_management/db_setup.sql].
|
||||
You can find table definitions there.
|
||||
|
||||
[import ../../example/order_management/prepared_statements_cpp14.cpp]
|
||||
|
||||
[example_prepared_statements_cpp14]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:stored_procedures_cpp11 Using stored procedures with the dynamic interface (C++11)]
|
||||
|
||||
This example demonstrates how to use stored procedures with the dynamic interface to
|
||||
implement a minimal order management system for an online store.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
This examples requires you to run [link_to_file example/order_management/db_setup.sql].
|
||||
You can find table and procedure definitions there.
|
||||
|
||||
[import ../../example/order_management/stored_procedures_cpp11.cpp]
|
||||
|
||||
[example_stored_procedures_cpp11]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:stored_procedures_cpp14 Using stored procedures with the static interface (C++14)]
|
||||
|
||||
This example demonstrates how to use stored procedures with the static interface to
|
||||
implement a minimal order management system for an online store.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
This examples requires you to run [link_to_file example/order_management/db_setup.sql].
|
||||
You can find table and procedure definitions there.
|
||||
|
||||
[import ../../example/order_management/stored_procedures_cpp14.cpp]
|
||||
|
||||
[example_stored_procedures_cpp14]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:metadata Metadata]
|
||||
|
||||
This example demonstrates how to use the available metadata in a [reflink results] object.
|
||||
@ -94,6 +249,9 @@ __assume_setup__
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
[section:unix_socket UNIX sockets]
|
||||
|
||||
This example demonstrates how to establish a connection
|
||||
@ -112,86 +270,8 @@ __assume_setup__
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:async_callbacks Async functions using callbacks]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using callbacks.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_callbacks.cpp]
|
||||
[example_async_callbacks]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:async_futures Async functions using futures]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using futures.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_futures.cpp]
|
||||
[example_async_futures]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:async_coroutines Async functions using stackful coroutines]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using stackful coroutines
|
||||
(using [asioreflink yield_context yield_context] and
|
||||
[asioreflink spawn spawn]).
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_coroutines.cpp]
|
||||
[example_async_coroutines]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:async_coroutinescpp20 Async functions using C++20 coroutines]
|
||||
|
||||
This example demonstrates how use the asynchronous functions using C++20 coroutines
|
||||
(using [asioreflink use_awaitable use_awaitable] and [asioreflink
|
||||
co_spawn co_spawn]).
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/async_coroutinescpp20.cpp]
|
||||
[example_async_coroutinescpp20]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:default_completion_tokens Async functions using default completion tokens]
|
||||
|
||||
This example demonstrates how to use Boost.Asio's
|
||||
default completion token functionality with __Self__.
|
||||
For that purpose, it employs C++20 coroutines.
|
||||
If you are not familiar with them, look at
|
||||
[link mysql.examples.async_coroutinescpp20 this example]
|
||||
first.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/default_completion_tokens.cpp]
|
||||
[example_default_completion_tokens]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:timeouts Timeouts]
|
||||
|
||||
This example demonstrates how to use Boost.Asio's
|
||||
cancellation features to add timeouts to your async operations,
|
||||
including the ones provided by __Self__.
|
||||
For that purpose, it employs C++20 coroutines.
|
||||
If you are not familiar with them, look at
|
||||
[link mysql.examples.async_coroutinescpp20 this example]
|
||||
first.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/timeouts.cpp]
|
||||
[example_timeouts]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:ssl Setting SSL options]
|
||||
|
||||
@ -213,22 +293,6 @@ for you.
|
||||
[endsect]
|
||||
|
||||
|
||||
[section:stored_procedures Using stored procedures]
|
||||
|
||||
This example demonstrates how to use stored procedures to
|
||||
implement a minimal order management system for an online store.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
This examples requires you to run [link_to_file example/db_setup_stored_procedures.sql].
|
||||
You can find table and procedure definitions in this file.
|
||||
|
||||
[import ../../example/stored_procedures.cpp]
|
||||
|
||||
[example_stored_procedures]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[section:source_script Using multi-queries to source a .sql file]
|
15
doc/qbk/helpers/ExecutionStateType.qbk
Normal file
15
doc/qbk/helpers/ExecutionStateType.qbk
Normal file
@ -0,0 +1,15 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:boost__mysql__ExecutionStateType ExecutionStateType concept]
|
||||
|
||||
A type `T` satisfies `ExecutionStateType` if either:
|
||||
|
||||
* It's exactly the [reflink execution_state] class.
|
||||
* It's an instantiation of the [reflink static_execution_state] template class.
|
||||
|
||||
[endsect]
|
@ -1,19 +0,0 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:boost__mysql__FieldLikeTuple FieldLikeTuple concept]
|
||||
|
||||
A type `T` is a `FieldLikeTuple` if it's a `std::tuple` specialization,
|
||||
or a reference to one, and all element types fulfill the exposition-only
|
||||
concept `FieldLike`. Empty tuples satisfy `FieldLikeTuple`.
|
||||
|
||||
Currently, a type `T2` satisfies `FieldLike` if a [reflink field_view]
|
||||
can be constructed from `T2`. The exact definition may change in future releases.
|
||||
|
||||
However, the types listed in [link mysql.fields.cpp_to_mysql this table] are guaranteed to satisfy `FieldLike`.
|
||||
|
||||
[endsect]
|
15
doc/qbk/helpers/ResultsType.qbk
Normal file
15
doc/qbk/helpers/ResultsType.qbk
Normal file
@ -0,0 +1,15 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:boost__mysql__ResultsType ResultsType concept]
|
||||
|
||||
A type `T` satisfies `ResultsType` if either:
|
||||
|
||||
* It's exactly the [reflink results] class.
|
||||
* It's an instantiation of the [reflink static_results] template class.
|
||||
|
||||
[endsect]
|
32
doc/qbk/helpers/StaticRow.qbk
Normal file
32
doc/qbk/helpers/StaticRow.qbk
Normal file
@ -0,0 +1,32 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:boost__mysql__StaticRow StaticRow concept]
|
||||
|
||||
A `StaticRow` is a C++ type that can be used to model a row within the
|
||||
static interface (i.e. [reflink static_results], [reflink static_execution_state]).
|
||||
|
||||
A type `T` is a `StaticRow` if either of the following is true:
|
||||
|
||||
* It is a `struct` annotated with Boost.Describe data (i.e., having
|
||||
`boost::describe::has_describe_members<T>::value == true`), and all the described
|
||||
members fulfill the `ReadableField` exposition-only concept.
|
||||
* It is a `std::tuple` instantiation, and all of its types fulfill the
|
||||
`ReadableField` exposition-only concept.
|
||||
|
||||
Note that empty Describe structs and empty tuples are valid `StaticRow`s.
|
||||
|
||||
A `ReadableField` is C++ type that can be used to model a single value in a database row.
|
||||
A type `F` is a `ReadableField` if it is any of the types listed
|
||||
[link mysql.static_interface.readable_field_reference in this table]. The set of readable field types
|
||||
is currently fixed and can't be extended by the user. If this is something you have interest in,
|
||||
[@https://github.com/boostorg/mysql/issues/new please file an issue] with your use case to the repo.
|
||||
|
||||
The set of allowable types may be extended in future releases, both for fields and for rows.
|
||||
|
||||
|
||||
[endsect]
|
20
doc/qbk/helpers/WritableFieldTuple.qbk
Normal file
20
doc/qbk/helpers/WritableFieldTuple.qbk
Normal file
@ -0,0 +1,20 @@
|
||||
[/
|
||||
Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:boost__mysql__WritableFieldTuple WritableFieldTuple concept]
|
||||
|
||||
A type `T` is a `WritableFieldTuple` if it's a `std::tuple` specialization,
|
||||
or a reference to one, and all element types fulfill the exposition-only
|
||||
concept `WritableField`. Empty tuples satisfy `WritableFieldTuple`.
|
||||
|
||||
A type is a `WritableField` if it can be used to represent a statement parameter
|
||||
value to be sent to the server. A type `T` satisfies `WritableField` if it is
|
||||
[link mysql.prepared_statements.writable_field_reference any of the types listed in this table],
|
||||
or a (possibly cv-qualified) reference to any of them.
|
||||
More types may be added in future releases.
|
||||
|
||||
[endsect]
|
@ -37,6 +37,8 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__rows">rows</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__rows_view">rows_view</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__statement">statement</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__static_execution_state">static_execution_state</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__static_results">static_results</link></member>
|
||||
</simplelist>
|
||||
</entry>
|
||||
<entry valign="top">
|
||||
@ -54,6 +56,17 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__unix_connection">unix_connection</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__unix_ssl_connection">unix_ssl_connection</link></member>
|
||||
</simplelist>
|
||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="mysql.ref.boost__mysql__ExecutionRequest">ExecutionRequest</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__ExecutionStateType">ExecutionStateType</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__FieldViewFwdIterator">FieldViewFwdIterator</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__ResultsType">ResultsType</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__SocketStream">SocketStream</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__StaticRow">StaticRow</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__Stream">Stream</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__WritableFieldTuple">WritableFieldTuple</link></member>
|
||||
</simplelist>
|
||||
</entry>
|
||||
<entry valign="top">
|
||||
<bridgehead renderas="sect3">Enumerations</bridgehead>
|
||||
@ -87,13 +100,12 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__make_error_code">make_error_code</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__throw_on_error">throw_on_error</link></member>
|
||||
</simplelist>
|
||||
<bridgehead renderas="sect3">Concepts</bridgehead>
|
||||
<bridgehead renderas="sect3">Reference tables</bridgehead>
|
||||
<simplelist type="vert" columns="1">
|
||||
<member><link linkend="mysql.ref.boost__mysql__ExecutionRequest">ExecutionRequest</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__FieldLikeTuple">FieldLikeTuple</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__FieldViewFwdIterator">FieldViewFwdIterator</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__SocketStream">SocketStream</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__Stream">Stream</link></member>
|
||||
<member><link linkend="mysql.dynamic_interface.dynamic_field_mappings">Dynamic interface type mappings</link></member>
|
||||
<member><link linkend="mysql.static_interface.readable_field_reference">ReadableField types</link></member>
|
||||
<member><link linkend="mysql.prepared_statements.writable_field_reference">WritableField types</link></member>
|
||||
<member><link linkend="mysql.charsets.string_encoding">String encoding</link></member>
|
||||
</simplelist>
|
||||
</entry>
|
||||
</row></tbody>
|
||||
|
@ -33,9 +33,12 @@
|
||||
'Stream',
|
||||
'SocketStream',
|
||||
'Executor',
|
||||
'FieldLikeTuple',
|
||||
'WritableFieldTuple',
|
||||
'FieldViewFwdIterator',
|
||||
'ExecutionRequest'
|
||||
'ExecutionRequest',
|
||||
'StaticRow',
|
||||
'ResultsType',
|
||||
'ExecutionStateType'
|
||||
"/>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
@ -25,23 +25,27 @@ target_link_libraries(
|
||||
boost_mysql_asio
|
||||
)
|
||||
|
||||
# Declare an example target
|
||||
function (add_example_target EXAMPLE_NAME EXAMPLE_PATH)
|
||||
add_executable(${EXAMPLE_NAME} ${EXAMPLE_PATH})
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_mysql_examples_common ${ARGN})
|
||||
common_target_settings(${EXAMPLE_NAME})
|
||||
endfunction()
|
||||
|
||||
# Build and run an example
|
||||
function (add_example EXAMPLE_NAME)
|
||||
set(EXECUTABLE_NAME "boost_mysql_example_${EXAMPLE_NAME}")
|
||||
add_executable(${EXECUTABLE_NAME} "${EXAMPLE_NAME}.cpp")
|
||||
target_link_libraries(${EXECUTABLE_NAME} PRIVATE boost_mysql_examples_common)
|
||||
common_target_settings(${EXECUTABLE_NAME})
|
||||
function (add_example EXAMPLE_NAME EXAMPLE_PATH)
|
||||
add_example_target(${EXAMPLE_NAME} ${EXAMPLE_PATH})
|
||||
|
||||
if (BOOST_MYSQL_VALGRIND_TESTS)
|
||||
add_memcheck_test(
|
||||
NAME "${EXECUTABLE_NAME}_memcheck"
|
||||
TARGET ${EXECUTABLE_NAME}
|
||||
NAME "${EXAMPLE_NAME}_memcheck"
|
||||
TARGET ${EXAMPLE_NAME}
|
||||
ARGUMENTS ${ARGN}
|
||||
)
|
||||
else()
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
COMMAND ${EXECUTABLE_NAME} ${ARGN}
|
||||
NAME ${EXAMPLE_NAME}
|
||||
COMMAND ${EXAMPLE_NAME} ${ARGN}
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
@ -49,59 +53,72 @@ endfunction()
|
||||
# Coroutines needs Boost.Context and shouldn't be memchecked
|
||||
function (add_example_coroutines)
|
||||
set(EXECUTABLE_NAME boost_mysql_example_async_coroutines)
|
||||
add_executable(${EXECUTABLE_NAME} async_coroutines.cpp)
|
||||
target_link_libraries(
|
||||
${EXECUTABLE_NAME}
|
||||
PRIVATE
|
||||
boost_mysql_examples_common
|
||||
Boost::context
|
||||
)
|
||||
common_target_settings(${EXECUTABLE_NAME})
|
||||
add_example_target(${EXECUTABLE_NAME} async_coroutines.cpp Boost::context)
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
COMMAND ${EXECUTABLE_NAME} example_user example_password ${SERVER_HOST}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# The stored procedures example must be run several times through a Python script
|
||||
function (add_example_stored_procedures)
|
||||
set(EXECUTABLE_NAME boost_mysql_example_stored_procedures)
|
||||
add_executable(${EXECUTABLE_NAME} stored_procedures.cpp)
|
||||
target_link_libraries(
|
||||
${EXECUTABLE_NAME}
|
||||
PRIVATE
|
||||
boost_mysql_examples_common
|
||||
)
|
||||
common_target_settings(${EXECUTABLE_NAME})
|
||||
# The order management examples must be run several times through a Python script
|
||||
function (add_example_order_management EXAMPLE_NAME EXAMPLE_PATH)
|
||||
add_example_target(${EXAMPLE_NAME} ${EXAMPLE_PATH})
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
NAME ${EXAMPLE_NAME}
|
||||
COMMAND
|
||||
python
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/private/run_stored_procedures.py
|
||||
$<TARGET_FILE:${EXECUTABLE_NAME}>
|
||||
$<TARGET_FILE:${EXAMPLE_NAME}>
|
||||
${SERVER_HOST}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# Build and run all the examples
|
||||
add_example(snippets example_user example_password ${SERVER_HOST})
|
||||
add_example(tutorial example_user example_password ${SERVER_HOST})
|
||||
add_example(text_queries example_user example_password ${SERVER_HOST})
|
||||
add_example(prepared_statements example_user example_password ${SERVER_HOST})
|
||||
add_example(async_callbacks example_user example_password ${SERVER_HOST})
|
||||
# Regular examples are the ones that require no extra linking libs and can be run
|
||||
# with example_user example_password
|
||||
set (REGULAR_EXAMPLES
|
||||
snippets
|
||||
tutorial
|
||||
async_callbacks
|
||||
async_coroutinescpp20
|
||||
async_futures
|
||||
default_completion_tokens
|
||||
metadata
|
||||
ssl
|
||||
timeouts
|
||||
)
|
||||
foreach (FILE_NAME ${REGULAR_EXAMPLES})
|
||||
add_example(
|
||||
"boost_mysql_example_${FILE_NAME}"
|
||||
"${FILE_NAME}.cpp"
|
||||
example_user example_password ${SERVER_HOST}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Order management examples must be run several times through a Python script
|
||||
set(ORDER_EXAMPLES
|
||||
prepared_statements_cpp11
|
||||
prepared_statements_cpp14
|
||||
stored_procedures_cpp11
|
||||
stored_procedures_cpp14
|
||||
)
|
||||
foreach (FILE_NAME ${ORDER_EXAMPLES})
|
||||
add_example_order_management(
|
||||
"boost_mysql_example_${FILE_NAME}"
|
||||
"order_management/${FILE_NAME}.cpp"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Rest of the examples
|
||||
add_example_coroutines()
|
||||
add_example(async_coroutinescpp20 example_user example_password ${SERVER_HOST})
|
||||
add_example(async_futures example_user example_password ${SERVER_HOST})
|
||||
add_example(default_completion_tokens example_user example_password ${SERVER_HOST})
|
||||
add_example(metadata example_user example_password ${SERVER_HOST})
|
||||
add_example(ssl example_user example_password ${SERVER_HOST})
|
||||
add_example(timeouts example_user example_password ${SERVER_HOST})
|
||||
add_example(source_script example_user example_password ${SERVER_HOST} ${CMAKE_CURRENT_SOURCE_DIR}/private/test_script.sql)
|
||||
add_example_stored_procedures()
|
||||
if ("$ENV{BOOST_MYSQL_NO_UNIX_SOCKET_TESTS}" STREQUAL "")
|
||||
add_example(unix_socket example_user example_password)
|
||||
add_example(
|
||||
boost_mysql_example_unix_socket
|
||||
unix_socket.cpp
|
||||
example_user example_password
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
add_example(
|
||||
boost_mysql_example_source_script
|
||||
source_script.cpp
|
||||
example_user example_password ${SERVER_HOST} ${CMAKE_CURRENT_SOURCE_DIR}/private/test_script.sql
|
||||
)
|
||||
|
@ -16,14 +16,11 @@ if $(hostname) = ""
|
||||
hostname = "localhost" ;
|
||||
}
|
||||
|
||||
# Example list
|
||||
# "Regular" examples
|
||||
local regular_examples =
|
||||
snippets
|
||||
tutorial
|
||||
text_queries
|
||||
prepared_statements
|
||||
async_callbacks
|
||||
async_coroutines
|
||||
async_coroutinescpp20
|
||||
async_futures
|
||||
default_completion_tokens
|
||||
@ -34,21 +31,46 @@ local regular_examples =
|
||||
|
||||
for local example in $(regular_examples)
|
||||
{
|
||||
local example_name = "boost_mysql_example_$(example)" ;
|
||||
unit-test $(example_name)
|
||||
:
|
||||
run
|
||||
"$(example).cpp"
|
||||
/boost/mysql/test//mysql
|
||||
/boost/context//boost_context
|
||||
:
|
||||
<testing.arg>"example_user example_password $(hostname)"
|
||||
;
|
||||
}
|
||||
|
||||
# Order management examples. These must be run several times through a Python script
|
||||
local order_examples =
|
||||
prepared_statements_cpp11
|
||||
prepared_statements_cpp14
|
||||
stored_procedures_cpp11
|
||||
stored_procedures_cpp14
|
||||
;
|
||||
|
||||
for local example in $(order_examples)
|
||||
{
|
||||
run
|
||||
"order_management/$(example).cpp"
|
||||
/boost/mysql/test//mysql
|
||||
: requirements
|
||||
<testing.launcher>"python $(this_dir)/private/run_stored_procedures.py"
|
||||
<testing.arg>$(hostname)
|
||||
: target-name "boost_mysql_example_$(example)"
|
||||
;
|
||||
}
|
||||
|
||||
# Coroutines, which requires Boost.Context
|
||||
run
|
||||
async_coroutines.cpp
|
||||
/boost/mysql/test//mysql
|
||||
/boost/context//boost_context
|
||||
:
|
||||
<testing.arg>"example_user example_password $(hostname)"
|
||||
;
|
||||
|
||||
# UNIX. Honor BOOST_MYSQL_NO_UNIX_SOCKET_TESTS for homogeneity with cmake
|
||||
if [ os.environ BOOST_MYSQL_NO_UNIX_SOCKET_TESTS ] = "" {
|
||||
unit-test boost_mysql_example_unix_socket
|
||||
:
|
||||
run
|
||||
unix_socket.cpp
|
||||
/boost/mysql/test//mysql
|
||||
:
|
||||
@ -56,21 +78,8 @@ if [ os.environ BOOST_MYSQL_NO_UNIX_SOCKET_TESTS ] = "" {
|
||||
;
|
||||
}
|
||||
|
||||
# Stored procedures. The example is a command line tool that must be run
|
||||
# several times in order, so we do this through a Python script
|
||||
unit-test boost_mysql_example_stored_procedures
|
||||
:
|
||||
stored_procedures.cpp
|
||||
/boost/mysql/test//mysql
|
||||
: requirements
|
||||
<testing.launcher>"python $(this_dir)/private/run_stored_procedures.py"
|
||||
<testing.arg>$(hostname)
|
||||
;
|
||||
|
||||
|
||||
# Source script. unit-test doesn't support input-file correctly
|
||||
unit-test boost_mysql_example_source_script
|
||||
:
|
||||
# Source script
|
||||
run
|
||||
source_script.cpp
|
||||
/boost/mysql/test//mysql
|
||||
: requirements
|
||||
|
@ -16,7 +16,8 @@ USE boost_mysql_examples;
|
||||
-- Tables
|
||||
CREATE TABLE company(
|
||||
id CHAR(10) NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL
|
||||
name VARCHAR(100) NOT NULL,
|
||||
tax_id VARCHAR(50) NOT NULL
|
||||
);
|
||||
CREATE TABLE employee(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
@ -32,10 +33,10 @@ CREATE TABLE audit_log(
|
||||
msg TEXT
|
||||
);
|
||||
|
||||
INSERT INTO company (name, id) VALUES
|
||||
("Award Winning Company, Inc.", "AWC"),
|
||||
("Sector Global Leader Plc", "SGL"),
|
||||
("High Growth Startup, Ltd", "HGS")
|
||||
INSERT INTO company (name, id, tax_id) VALUES
|
||||
("Award Winning Company, Inc.", "AWC", "IE1234567V"),
|
||||
("Sector Global Leader Plc", "SGL", "IE1234568V"),
|
||||
("High Growth Startup, Ltd", "HGS", "IE1234569V")
|
||||
;
|
||||
INSERT INTO employee (first_name, last_name, salary, company_id) VALUES
|
||||
("Efficient", "Developer", 30000, "AWC"),
|
||||
@ -46,6 +47,34 @@ INSERT INTO employee (first_name, last_name, salary, company_id) VALUES
|
||||
("Underpaid", "Intern", 15000, "AWC")
|
||||
;
|
||||
|
||||
-- Stored procedures
|
||||
DELIMITER //
|
||||
|
||||
CREATE PROCEDURE get_employees(IN pin_company_id CHAR(10))
|
||||
BEGIN
|
||||
START TRANSACTION READ ONLY;
|
||||
SELECT id, name, tax_id FROM company WHERE id = pin_company_id;
|
||||
SELECT first_name, last_name, salary FROM employee WHERE company_id = pin_company_id;
|
||||
COMMIT;
|
||||
END//
|
||||
|
||||
CREATE PROCEDURE create_employee(
|
||||
IN pin_company_id CHAR(10),
|
||||
IN pin_first_name VARCHAR(100),
|
||||
IN pin_last_name VARCHAR(100),
|
||||
OUT pout_employee_id INT
|
||||
)
|
||||
BEGIN
|
||||
START TRANSACTION;
|
||||
INSERT INTO employee (company_id, first_name, last_name)
|
||||
VALUES (pin_company_id, pin_first_name, pin_last_name);
|
||||
SET pout_employee_id = LAST_INSERT_ID();
|
||||
INSERT INTO audit_log (msg) VALUES ('Created new employee...');
|
||||
COMMIT;
|
||||
END//
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
-- User
|
||||
DROP USER IF EXISTS 'example_user'@'%';
|
||||
CREATE USER 'example_user'@'%' IDENTIFIED WITH 'mysql_native_password';
|
||||
|
@ -9,15 +9,15 @@
|
||||
SET NAMES utf8;
|
||||
|
||||
-- Database
|
||||
DROP DATABASE IF EXISTS boost_mysql_stored_procedures;
|
||||
CREATE DATABASE boost_mysql_stored_procedures;
|
||||
USE boost_mysql_stored_procedures;
|
||||
DROP DATABASE IF EXISTS boost_mysql_order_management;
|
||||
CREATE DATABASE boost_mysql_order_management;
|
||||
USE boost_mysql_order_management;
|
||||
|
||||
-- User
|
||||
DROP USER IF EXISTS 'sp_user'@'%';
|
||||
CREATE USER 'sp_user'@'%' IDENTIFIED WITH 'mysql_native_password';
|
||||
ALTER USER 'sp_user'@'%' IDENTIFIED BY 'sp_password';
|
||||
GRANT ALL PRIVILEGES ON boost_mysql_stored_procedures.* TO 'sp_user'@'%';
|
||||
DROP USER IF EXISTS 'orders_user'@'%';
|
||||
CREATE USER 'orders_user'@'%' IDENTIFIED WITH 'mysql_native_password';
|
||||
ALTER USER 'orders_user'@'%' IDENTIFIED BY 'orders_password';
|
||||
GRANT ALL PRIVILEGES ON boost_mysql_order_management.* TO 'orders_user'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Table definitions
|
||||
@ -46,7 +46,7 @@ CREATE TABLE order_items(
|
||||
-- Procedures
|
||||
DELIMITER //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_products(IN p_search VARCHAR(50))
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE get_products(IN p_search VARCHAR(50))
|
||||
BEGIN
|
||||
DECLARE max_products INT DEFAULT 20;
|
||||
IF p_search IS NULL THEN
|
||||
@ -62,22 +62,20 @@ END //
|
||||
|
||||
CREATE PROCEDURE create_order()
|
||||
BEGIN
|
||||
DECLARE new_order_id INT;
|
||||
START TRANSACTION;
|
||||
|
||||
-- Create the order
|
||||
INSERT INTO orders () VALUES ();
|
||||
SET new_order_id = LAST_INSERT_ID();
|
||||
|
||||
-- Return the order
|
||||
SELECT
|
||||
new_order_id AS id,
|
||||
'draft' AS `status`;
|
||||
SELECT id, `status`
|
||||
FROM orders
|
||||
WHERE id = LAST_INSERT_ID();
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_order(
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE get_order(
|
||||
IN p_order_id INT
|
||||
)
|
||||
BEGIN
|
||||
@ -97,10 +95,10 @@ BEGIN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given order does not exist';
|
||||
END IF;
|
||||
|
||||
-- Return the order
|
||||
-- Return the order. The IFNULL statements make MySQL correctly report the fields as non-NULL
|
||||
SELECT
|
||||
p_order_id AS id,
|
||||
order_status AS `status`;
|
||||
IFNULL(p_order_id, 0) AS id,
|
||||
IFNULL(order_status, 'draft') AS `status`;
|
||||
SELECT
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
@ -112,12 +110,12 @@ BEGIN
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_orders()
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE get_orders()
|
||||
BEGIN
|
||||
SELECT id, `status` FROM orders;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE add_line_item(
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE add_line_item(
|
||||
IN p_order_id INT,
|
||||
IN p_product_id INT,
|
||||
IN p_quantity INT,
|
||||
@ -145,7 +143,7 @@ BEGIN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given order does not exist';
|
||||
END IF;
|
||||
IF order_status <> 'draft' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT = 'The given is not editable';
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT = 'The given order is not editable';
|
||||
END IF;
|
||||
|
||||
-- Insert the new item
|
||||
@ -168,7 +166,7 @@ BEGIN
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE remove_line_item(
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE remove_line_item(
|
||||
IN p_line_item_id INT
|
||||
)
|
||||
BEGIN
|
||||
@ -213,7 +211,7 @@ BEGIN
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE checkout_order(
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE checkout_order(
|
||||
IN p_order_id INT,
|
||||
OUT pout_order_total INT
|
||||
)
|
||||
@ -252,7 +250,7 @@ BEGIN
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = p_order_id;
|
||||
SELECT
|
||||
item.id AS item_id,
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
@ -263,7 +261,7 @@ BEGIN
|
||||
END //
|
||||
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE complete_order(
|
||||
CREATE DEFINER = 'orders_user'@'%' PROCEDURE complete_order(
|
||||
IN p_order_id INT
|
||||
)
|
||||
BEGIN
|
||||
@ -294,7 +292,7 @@ BEGIN
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = p_order_id;
|
||||
SELECT
|
||||
item.id AS item_id,
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
200
example/order_management/parse_cmdline.hpp
Normal file
200
example/order_management/parse_cmdline.hpp
Normal file
@ -0,0 +1,200 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 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_EXAMPLE_ORDER_MANAGEMENT_PARSE_CMDLINE_HPP
|
||||
#define BOOST_MYSQL_EXAMPLE_ORDER_MANAGEMENT_PARSE_CMDLINE_HPP
|
||||
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Our command line tool implements several sub-commands. Each sub-command
|
||||
* has a set of arguments. We define a struct for each sub-command.
|
||||
*/
|
||||
|
||||
struct get_products_args
|
||||
{
|
||||
std::string search;
|
||||
};
|
||||
|
||||
struct create_order_args
|
||||
{
|
||||
};
|
||||
|
||||
struct get_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
struct get_orders_args
|
||||
{
|
||||
};
|
||||
|
||||
struct add_line_item_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
std::int64_t product_id;
|
||||
std::int64_t quantity;
|
||||
};
|
||||
|
||||
struct remove_line_item_args
|
||||
{
|
||||
std::int64_t line_item_id;
|
||||
};
|
||||
|
||||
struct checkout_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
struct complete_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
// A variant type that can represent arguments for any of the sub-commands
|
||||
using any_command = boost::variant2::variant<
|
||||
get_products_args,
|
||||
get_order_args,
|
||||
get_orders_args,
|
||||
create_order_args,
|
||||
add_line_item_args,
|
||||
remove_line_item_args,
|
||||
checkout_order_args,
|
||||
complete_order_args>;
|
||||
|
||||
// In-memory representation of the command-line arguments once parsed.
|
||||
struct cmdline_args
|
||||
{
|
||||
const char* username;
|
||||
const char* password;
|
||||
const char* host;
|
||||
any_command cmd;
|
||||
};
|
||||
|
||||
// Call on error to print usage and exit
|
||||
[[noreturn]] inline void usage(boost::mysql::string_view program_name)
|
||||
{
|
||||
std::cerr << "Usage: " << program_name << " <username> <password> <server-hostname> <command> args...\n"
|
||||
<< "Available commands:\n"
|
||||
" get-products <search-term>\n"
|
||||
" create-order\n"
|
||||
" get-order <order-id>\n"
|
||||
" get-orders\n"
|
||||
" add-line-item <order-id> <product-id> <quantity>\n"
|
||||
" remove-line-item <line-item-id>\n"
|
||||
" checkout-order <order-id>\n"
|
||||
" complete-order <order-id>"
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Helper function to parse a sub-command
|
||||
inline any_command parse_subcommand(
|
||||
boost::mysql::string_view program_name,
|
||||
boost::mysql::string_view cmd_name,
|
||||
int argc_rest,
|
||||
char** argv_rest
|
||||
)
|
||||
{
|
||||
if (cmd_name == "get-products")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_products_args{argv_rest[0]};
|
||||
}
|
||||
else if (cmd_name == "create-order")
|
||||
{
|
||||
if (argc_rest != 0)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return create_order_args{};
|
||||
}
|
||||
else if (cmd_name == "get-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else if (cmd_name == "get-orders")
|
||||
{
|
||||
if (argc_rest != 0)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_orders_args{};
|
||||
}
|
||||
else if (cmd_name == "add-line-item")
|
||||
{
|
||||
if (argc_rest != 3)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return add_line_item_args{
|
||||
std::stoi(argv_rest[0]),
|
||||
std::stoi(argv_rest[1]),
|
||||
std::stoi(argv_rest[2]),
|
||||
};
|
||||
}
|
||||
else if (cmd_name == "remove-line-item")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return remove_line_item_args{
|
||||
std::stoi(argv_rest[0]),
|
||||
};
|
||||
}
|
||||
else if (cmd_name == "checkout-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return checkout_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else if (cmd_name == "complete-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return complete_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses the entire command line
|
||||
inline cmdline_args parse_cmdline_args(int argc, char** argv)
|
||||
{
|
||||
if (argc < 5)
|
||||
{
|
||||
usage(argv[0]);
|
||||
}
|
||||
return cmdline_args{
|
||||
argv[1],
|
||||
argv[2],
|
||||
argv[3],
|
||||
parse_subcommand(argv[0], argv[4], argc - 5, argv + 5),
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
437
example/order_management/prepared_statements_cpp11.cpp
Normal file
437
example/order_management/prepared_statements_cpp11.cpp
Normal file
@ -0,0 +1,437 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
//[example_prepared_statements_cpp11
|
||||
|
||||
/**
|
||||
* This example implements a very simple command-line order manager
|
||||
* for an online store, using prepared statements. You can find the table
|
||||
* definitions in example/order_management/db_setup.sql. Be sure to run this file before the example.
|
||||
* This example assumes you are connecting to a localhost MySQL server.
|
||||
*
|
||||
* The order system is intentionally very simple, and has the following tables:
|
||||
* - products: the list of items our store sells, with price and description.
|
||||
* - orders: the main object. Orders have a status field that can be draft, pending_payment or complete.
|
||||
* - order_items: an order may have 0 to n line items. Each item refers to a single product.
|
||||
*
|
||||
* Orders are created empty, in a draft state. Line items can be added or removed.
|
||||
* Orders are then checked out, which transitions them to pending_payment.
|
||||
* After that, payment would happen through an external system. Once completed, an
|
||||
* order is confirmed, transitioning it to the complete status.
|
||||
* In the real world, flow would be much more complex, but this is enough for an example.
|
||||
*
|
||||
* We'll be using the untyped interface to retrieve results from MySQL.
|
||||
* This makes use of the results, rows_view, row_view and field_view classes.
|
||||
* If you prefer typing your rows statically, you may prefer using the "typed interface",
|
||||
* which uses static_results instead.
|
||||
*/
|
||||
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/tcp_ssl.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
// This header contains boilerplate code to parse the command line
|
||||
// arguments into structs. Parsing the command line yields a cmdline_args,
|
||||
// an alias for a boost::variant2::variant holding the command line
|
||||
// arguments for any of the subcommands. We will use it via visit().
|
||||
#include "parse_cmdline.hpp"
|
||||
|
||||
namespace mysql = boost::mysql;
|
||||
|
||||
namespace {
|
||||
|
||||
// This visitor executes a sub-command and prints the results to stdout.
|
||||
struct visitor
|
||||
{
|
||||
mysql::tcp_ssl_connection& conn;
|
||||
|
||||
// Retrieves an order with its items. If the order exists, at least one record is returned.
|
||||
// If the order has line items, a record per item is returned. If the order has no items,
|
||||
// a single record is returned, and it will have its item_xxx fields set to NULL.
|
||||
mysql::results get_order_with_items(std::int64_t order_id) const
|
||||
{
|
||||
// Prepare a statement, since the order_id is provided by the user
|
||||
mysql::statement stmt = conn.prepare_statement(R"%(
|
||||
SELECT
|
||||
ord.id AS order_id,
|
||||
ord.status AS order_status,
|
||||
item.id AS item_id,
|
||||
item.quantity AS item_quantity,
|
||||
prod.price AS item_unit_price
|
||||
FROM orders ord
|
||||
LEFT JOIN order_items item ON ord.id = item.order_id
|
||||
LEFT JOIN products prod ON item.product_id = prod.id
|
||||
WHERE ord.id = ?
|
||||
)%");
|
||||
|
||||
// Execute it
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(order_id), result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prints an order with its line items to stdout. Each row has the format
|
||||
// described in get_order_with_items
|
||||
static void print_order_with_items(mysql::rows_view ord_items)
|
||||
{
|
||||
// Print the order
|
||||
std::cout << "Order: id=" << ord_items.at(0).at(0) << ", status=" << ord_items.at(0).at(1) << '\n';
|
||||
|
||||
// Print the items (3rd to 5th fields). These will be NULL if the order
|
||||
// contains no items.
|
||||
if (ord_items.at(0).at(2).is_null())
|
||||
{
|
||||
std::cout << "No line items\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (mysql::row_view item : ord_items)
|
||||
{
|
||||
std::cout << " Line item: id=" << item.at(2) << ", quantity=" << item.at(3)
|
||||
<< ", unit_price=" << item.at(4).as_int64() / 100.0 << "$\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get-products <search-term>: full text search of the products table.
|
||||
// use this command to search the store for available products
|
||||
void operator()(const get_products_args& args) const
|
||||
{
|
||||
// Our SQL contains a user-supplied paremeter (the search term),
|
||||
// so we will be using a prepared statement
|
||||
mysql::statement stmt = conn.prepare_statement(R"%(
|
||||
SELECT id, short_name, descr, price
|
||||
FROM products
|
||||
WHERE MATCH(short_name, descr) AGAINST(?)
|
||||
LIMIT 5
|
||||
)%");
|
||||
|
||||
// Execute it
|
||||
mysql::results products;
|
||||
conn.execute(stmt.bind(args.search), products);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Your search returned the following products:\n";
|
||||
for (mysql::row_view prod : products.rows())
|
||||
{
|
||||
std::cout << "* ID: " << prod.at(0) << '\n'
|
||||
<< " Short name: " << prod.at(1) << '\n'
|
||||
<< " Description: " << prod.at(2) << '\n'
|
||||
<< " Price: " << prod.at(3).as_int64() / 100.0 << "$" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// create-order: creates a new order. Orders are always created empty. This command
|
||||
// requires no arguments
|
||||
void operator()(const create_order_args&) const
|
||||
{
|
||||
// Our SQL doesn't have parameters, so we can use a text query.
|
||||
mysql::results result;
|
||||
conn.execute("INSERT INTO orders VALUES ()", result);
|
||||
|
||||
// Print the results to stdout. results::last_insert_id() returns the ID of
|
||||
// the newly inserted order.
|
||||
std::cout << "Order: id=" << result.last_insert_id() << ", status=draft" << std::endl;
|
||||
}
|
||||
|
||||
// get-order <order-id>: retrieves order details
|
||||
void operator()(const get_order_args& args) const
|
||||
{
|
||||
// Retrieve the order with its items
|
||||
mysql::results result = get_order_with_items(args.order_id);
|
||||
|
||||
// If we didn't find any order, issue an error
|
||||
if (result.rows().empty())
|
||||
{
|
||||
throw std::runtime_error("Can't find order with id=" + std::to_string(args.order_id));
|
||||
}
|
||||
|
||||
// Print the order to stdout
|
||||
std::cout << "Retrieved order\n";
|
||||
print_order_with_items(result.rows());
|
||||
}
|
||||
|
||||
// get-orders: lists all orders. Orders are listed without their line items.
|
||||
void operator()(const get_orders_args&) const
|
||||
{
|
||||
// Since this query doesn't have parameters, we don't need a prepared statement,
|
||||
// and we can use a text query instead.
|
||||
mysql::results result;
|
||||
conn.execute("SELECT id, `status` FROM orders", result);
|
||||
|
||||
// Print the results to stdout
|
||||
if (result.rows().empty())
|
||||
{
|
||||
std::cout << "No orders found" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (mysql::row_view order : result.rows())
|
||||
{
|
||||
std::cout << "Order: id=" << order.at(0) << ", status=" << order.at(1) << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add-line-item <order-id> <product-id> <quantity>: adds a line item to a given order
|
||||
void operator()(const add_line_item_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::results result;
|
||||
conn.execute("START TRANSACTION", result);
|
||||
|
||||
// To add a line item, we require the order to be in a draft status. Get the order to check this fact.
|
||||
mysql::statement stmt = conn.prepare_statement("SELECT `status` FROM orders WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
if (result.rows().empty())
|
||||
{
|
||||
// There is no such order
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (result.rows()[0].at(0).as_string() != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " is not editable");
|
||||
}
|
||||
|
||||
// Insert the new line item. If the given product does not exist, the INSERT will fail
|
||||
// because of product_id's FOREIGN KEY constraint.
|
||||
stmt = conn.prepare_statement(
|
||||
"INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)"
|
||||
);
|
||||
conn.execute(stmt.bind(args.order_id, args.product_id, args.quantity), result);
|
||||
|
||||
// We can use results::last_insert_id to get the ID of the new line item.
|
||||
auto new_line_item_id = result.last_insert_id();
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::results order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", result);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Created line item: id=" << new_line_item_id << "\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// remove-line-item <line-item-id>: removes an item from an order
|
||||
void operator()(const remove_line_item_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::results result;
|
||||
conn.execute("START TRANSACTION", result);
|
||||
|
||||
// To remove a line item, we require the order to be in a draft status. Get the order to check it.
|
||||
auto stmt = conn.prepare_statement(R"%(
|
||||
SELECT orders.id, orders.`status`
|
||||
FROM orders
|
||||
JOIN order_items items ON (orders.id = items.order_id)
|
||||
WHERE items.id = ?
|
||||
)%");
|
||||
conn.execute(stmt.bind(args.line_item_id), result);
|
||||
if (result.rows().empty())
|
||||
{
|
||||
// The query hasn't matched any row - the supplied line item ID is not valid
|
||||
throw std::runtime_error(
|
||||
"The order item with id=" + std::to_string(args.line_item_id) + " does not exist"
|
||||
);
|
||||
}
|
||||
mysql::row_view order = result.rows()[0];
|
||||
if (order.at(1).as_string() != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error("The order is not in an editable state");
|
||||
}
|
||||
|
||||
// Remove the line item
|
||||
stmt = conn.prepare_statement("DELETE FROM order_items WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.line_item_id), result);
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::results order_results = get_order_with_items(order.at(0).as_int64());
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", result);
|
||||
|
||||
// Print results to stdout
|
||||
std::cout << "Removed line item from order\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// checkout-order <order-id>: marks an order as ready for checkout
|
||||
void operator()(const checkout_order_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::results result;
|
||||
conn.execute("START TRANSACTION", result);
|
||||
|
||||
// To checkout an order, we require it to be in a draft status. Check this fact.
|
||||
mysql::statement stmt = conn.prepare_statement("SELECT `status` FROM orders WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
if (result.rows().empty())
|
||||
{
|
||||
// No order matched
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (result.rows()[0].at(0).as_string() != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error(
|
||||
"Order with id=" + std::to_string(args.order_id) + " cannot be checked out"
|
||||
);
|
||||
}
|
||||
|
||||
// Update the order status
|
||||
stmt = conn.prepare_statement("UPDATE orders SET `status` = 'pending_payment' WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Calculate the total amount to pay. SUM() returns a DECIMAL, which has a bigger
|
||||
// range than integers. DECIMAL is represented in C++ as a string. We use CAST to obtain
|
||||
// an uint64_t. If the CAST overflows, the max value for uint64_t will be returned.
|
||||
// We will be limiting our orders to USD 1bn, so overflow will be detected.
|
||||
stmt = conn.prepare_statement(R"%(
|
||||
SELECT CAST(
|
||||
IFNULL(SUM(prod.price * item.quantity), 0)
|
||||
AS UNSIGNED
|
||||
)
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = ?;
|
||||
)%");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
std::uint64_t total_amount = result.rows().at(0).at(0).as_uint64();
|
||||
|
||||
// Verify that the total amount meets our criteria
|
||||
if (total_amount == 0)
|
||||
{
|
||||
throw std::runtime_error("The order doesn't have any line item");
|
||||
}
|
||||
else if (total_amount > 1000 * 1000 * 100)
|
||||
{
|
||||
throw std::runtime_error("Order amount of " + std::to_string(total_amount) + " exceeds limit");
|
||||
}
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::results order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", result);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Checked out order. The total amount to pay is: " << total_amount / 100.0 << "$\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// complete-order <order-id>: marks an order as completed
|
||||
void operator()(const complete_order_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::results result;
|
||||
conn.execute("START TRANSACTION", result);
|
||||
|
||||
// To complete an order, we require it to be in a pending_payment status. Check this fact.
|
||||
auto stmt = conn.prepare_statement("SELECT `status` FROM orders WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
if (result.rows().empty())
|
||||
{
|
||||
// Order not found
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (result.rows()[0].at(0).as_string() != "pending_payment")
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Order with id=" + std::to_string(args.order_id) + " is not in pending_payment status"
|
||||
);
|
||||
}
|
||||
|
||||
// Update status
|
||||
stmt = conn.prepare_statement("UPDATE orders SET `status` = 'complete' WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::results order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", result);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Completed order\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
};
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
// Parse command line arguments
|
||||
auto args = parse_cmdline_args(argc, argv);
|
||||
|
||||
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Connection params
|
||||
mysql::handshake_params params(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_order_management" // database to use
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(args.host, mysql::default_port_string);
|
||||
|
||||
// TCP and MySQL level connect
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
// Execute the command
|
||||
boost::variant2::visit(visitor{conn}, args.cmd);
|
||||
|
||||
// Close the connection
|
||||
conn.close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
|
||||
// field value that caused the error) and is encoded using to the connection's encoding
|
||||
// (UTF-8 by default). Treat is as untrusted input.
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n'
|
||||
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
538
example/order_management/prepared_statements_cpp14.cpp
Normal file
538
example/order_management/prepared_statements_cpp14.cpp
Normal file
@ -0,0 +1,538 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
//[example_prepared_statements_cpp14
|
||||
|
||||
/**
|
||||
* This example implements a very simple command-line order manager
|
||||
* for an online store, using prepared statements. You can find the table
|
||||
* definitions in example/order_management/db_setup.sql. Be sure to run this file before the example.
|
||||
* This example assumes you are connecting to a localhost MySQL server.
|
||||
*
|
||||
* The order system is intentionally very simple, and has the following tables:
|
||||
* - products: the list of items our store sells, with price and description.
|
||||
* - orders: the main object. Orders have a status field that can be draft, pending_payment or complete.
|
||||
* - order_items: an order may have 0 to n line items. Each item refers to a single product.
|
||||
*
|
||||
* Orders are created empty, in a draft state. Line items can be added or removed.
|
||||
* Orders are then checked out, which transitions them to pending_payment.
|
||||
* After that, payment would happen through an external system. Once completed, an
|
||||
* order is confirmed, transitioning it to the complete status.
|
||||
* In the real world, flow would be much more complex, but this is enough for an example.
|
||||
*
|
||||
* We'll be using the static interface to retrieve results from MySQL.
|
||||
* This makes use of the static_results<RowType> class template.
|
||||
* To use it, we need to define a set of structs/tuples describing the shape
|
||||
* of our rows. Boost.MySQL will parse the received rows into these types.
|
||||
* The static interface requires C++14 to work.
|
||||
*
|
||||
* Row types may be plain structs or std::tuple's. If we use plain structs, we need
|
||||
* to use BOOST_DESCRIBE_STRUCT on them. This adds the structs the required reflection
|
||||
* data, so Boost.MySQL knows how to parse rows into them.
|
||||
*/
|
||||
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/static_results.hpp>
|
||||
#include <boost/mysql/tcp_ssl.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/describe/class.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
// This header contains boilerplate code to parse the command line
|
||||
// arguments into structs. Parsing the command line yields a cmdline_args,
|
||||
// an alias for a boost::variant2::variant holding the command line
|
||||
// arguments for any of the subcommands. We will use it via visit().
|
||||
#include "parse_cmdline.hpp"
|
||||
|
||||
// Including any of the static interface headers brings this macro into
|
||||
// scope if the static interface is supported.
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
namespace mysql = boost::mysql;
|
||||
|
||||
namespace {
|
||||
|
||||
// An order retrieved by our system.
|
||||
struct order
|
||||
{
|
||||
// The unique database ID of the object.
|
||||
std::int64_t id;
|
||||
|
||||
// The order status (draft, pending_payment, complete).
|
||||
std::string status;
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(order, (), (id, status))
|
||||
|
||||
// A product, as listed in the store product catalog.
|
||||
struct product
|
||||
{
|
||||
// The unique database ID of the object.
|
||||
std::int64_t id;
|
||||
// A short name for the product. Can be used as a title.
|
||||
std::string short_name;
|
||||
|
||||
// The product's description. This field can be NULL in the DB,
|
||||
// so we use boost::optional<T> for it. If you're using C++17 or higher,
|
||||
// you can use std::optional instead.
|
||||
boost::optional<std::string> descr;
|
||||
|
||||
// The product's unit price, in cents of USD.
|
||||
std::int64_t price;
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(product, (), (id, short_name, descr, price))
|
||||
|
||||
// An order with its line items. This record type is returned by JOINs
|
||||
// from the orders and order_items tables. We use this type to retrieve both
|
||||
// an order and its line items in a single operation.
|
||||
// If the order contains no line items, the item_xxx fields are NULL.
|
||||
struct order_with_items
|
||||
{
|
||||
// The ID of the order
|
||||
std::int64_t order_id;
|
||||
|
||||
// The status of the order
|
||||
std::string order_status;
|
||||
|
||||
// The ID of the line item, or NULL if the order doesn't have any
|
||||
boost::optional<std::int64_t> item_id;
|
||||
|
||||
// The number of units of this product that the user wants to buy,
|
||||
// or NULL if the order doesn't have line items
|
||||
boost::optional<std::int64_t> item_quantity;
|
||||
|
||||
// The product's unit price, in cents of USD, or NULL if the order
|
||||
// doesn't have line items
|
||||
boost::optional<std::int64_t> item_unit_price;
|
||||
|
||||
bool has_item() const
|
||||
{
|
||||
return item_id.has_value() && item_quantity.has_value() && item_unit_price.has_value();
|
||||
}
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(
|
||||
order_with_items,
|
||||
(),
|
||||
(order_id, order_status, item_id, item_quantity, item_unit_price)
|
||||
);
|
||||
|
||||
// An empty row type. This can be used to describe empty resultsets,
|
||||
// like the ones returned by INSERT or CALL.
|
||||
using empty = std::tuple<>;
|
||||
|
||||
// This visitor executes a sub-command and prints the results to stdout.
|
||||
struct visitor
|
||||
{
|
||||
mysql::tcp_ssl_connection& conn;
|
||||
|
||||
static void print_order(const order& ord)
|
||||
{
|
||||
std::cout << "Order: id=" << ord.id << ", status=" << ord.status << '\n';
|
||||
}
|
||||
|
||||
// Retrieves an order with its items. If the order exists, at least one record is returned.
|
||||
// If the order has line items, a record per item is returned. If the order has no items,
|
||||
// a single record is returned, and it will have its item_xxx fields set to NULL.
|
||||
mysql::static_results<order_with_items> get_order_with_items(std::int64_t order_id) const
|
||||
{
|
||||
mysql::statement stmt = conn.prepare_statement(R"%(
|
||||
SELECT
|
||||
ord.id AS order_id,
|
||||
ord.status AS order_status,
|
||||
item.id AS item_id,
|
||||
item.quantity AS item_quantity,
|
||||
prod.price AS item_unit_price
|
||||
FROM orders ord
|
||||
LEFT JOIN order_items item ON ord.id = item.order_id
|
||||
LEFT JOIN products prod ON item.product_id = prod.id
|
||||
WHERE ord.id = ?
|
||||
)%");
|
||||
|
||||
mysql::static_results<order_with_items> result;
|
||||
conn.execute(stmt.bind(order_id), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prints an order with its line items to stdout
|
||||
static void print_order_with_items(boost::span<const order_with_items> ord_items)
|
||||
{
|
||||
assert(!ord_items.empty());
|
||||
|
||||
// Print the order
|
||||
std::cout << "Order: id=" << ord_items[0].order_id << ", status=" << ord_items[0].order_status
|
||||
<< '\n';
|
||||
|
||||
// Print the items
|
||||
if (!ord_items[0].has_item())
|
||||
{
|
||||
std::cout << "No line items\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& item : ord_items)
|
||||
{
|
||||
std::cout << " Line item: id=" << *item.item_id << ", quantity=" << *item.item_quantity
|
||||
<< ", unit_price=" << *item.item_unit_price / 100.0 << "$\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get-products <search-term>: full text search of the products table.
|
||||
// use this command to search the store for available products
|
||||
void operator()(const get_products_args& args) const
|
||||
{
|
||||
// Our SQL contains a user-supplied paremeter (the search term),
|
||||
// so we will be using a prepared statement
|
||||
mysql::statement stmt = conn.prepare_statement(R"%(
|
||||
SELECT id, short_name, descr, price
|
||||
FROM products
|
||||
WHERE MATCH(short_name, descr) AGAINST(?)
|
||||
LIMIT 5
|
||||
)%");
|
||||
|
||||
// The product struct describes the shape of the rows that
|
||||
// we expect the server to send.
|
||||
mysql::static_results<product> products;
|
||||
conn.execute(stmt.bind(args.search), products);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Your search returned the following products:\n";
|
||||
for (const product& prod : products.rows())
|
||||
{
|
||||
std::cout << "* ID: " << prod.id << '\n'
|
||||
<< " Short name: " << prod.short_name << '\n'
|
||||
<< " Description: " << (prod.descr ? *prod.descr : "") << '\n'
|
||||
<< " Price: " << prod.price / 100.0 << "$" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// create-order: creates a new order. Orders are always created empty. This command
|
||||
// requires no arguments
|
||||
void operator()(const create_order_args&) const
|
||||
{
|
||||
// Since this is an INSERT, we don't expect any row to be returned.
|
||||
// empty is an alias for std::tuple<>, which tells static_results to expect
|
||||
// an empty resultset.
|
||||
mysql::static_results<empty> result;
|
||||
conn.execute("INSERT INTO orders VALUES ()", result);
|
||||
|
||||
// We can use static_results::last_insert_id() to retrieve the ID of the newly
|
||||
// created object. last_insert_id() returns always a uint64_t. Our schema uses
|
||||
// plain INTs for the id field, so this cast is safe.
|
||||
order ord{static_cast<std::int64_t>(result.last_insert_id()), "draft"};
|
||||
print_order(ord);
|
||||
}
|
||||
|
||||
// get-order <order-id>: retrieves order details
|
||||
void operator()(const get_order_args& args) const
|
||||
{
|
||||
// Retrieve the order with its items
|
||||
mysql::static_results<order_with_items> result = get_order_with_items(args.order_id);
|
||||
|
||||
// If we didn't find any order, issue an error
|
||||
if (result.rows().empty())
|
||||
{
|
||||
throw std::runtime_error("Can't find order with id=" + std::to_string(args.order_id));
|
||||
}
|
||||
|
||||
// Print the order to stdout
|
||||
std::cout << "Retrieved order\n";
|
||||
print_order_with_items(result.rows());
|
||||
}
|
||||
|
||||
// get-orders: lists all orders. Orders are listed without their line items.
|
||||
void operator()(const get_orders_args&) const
|
||||
{
|
||||
// Since this query doesn't have parameters, we don't need a prepared statement,
|
||||
// and we can use a text query instead.
|
||||
mysql::static_results<order> result;
|
||||
conn.execute("SELECT id, `status` FROM orders", result);
|
||||
|
||||
// Print the results to stdout
|
||||
if (result.rows().empty())
|
||||
{
|
||||
std::cout << "No orders found" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const order& ord : result.rows())
|
||||
{
|
||||
print_order(ord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add-line-item <order-id> <product-id> <quantity>: adds a line item to a given order
|
||||
void operator()(const add_line_item_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::static_results<empty> empty_results;
|
||||
conn.execute("START TRANSACTION", empty_results);
|
||||
|
||||
// To add a line item, we require the order to be in a draft status. Get the order to check this fact.
|
||||
mysql::statement stmt = conn.prepare_statement("SELECT id, `status` FROM orders WHERE id = ?");
|
||||
mysql::static_results<order> orders;
|
||||
conn.execute(stmt.bind(args.order_id), orders);
|
||||
if (orders.rows().empty())
|
||||
{
|
||||
// There is no such order
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (orders.rows()[0].status != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " is not editable");
|
||||
}
|
||||
|
||||
// Insert the new line item. If the given product does not exist, the INSERT will fail
|
||||
// because of product_id's FOREIGN KEY constraint.
|
||||
stmt = conn.prepare_statement(
|
||||
"INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)"
|
||||
);
|
||||
conn.execute(stmt.bind(args.order_id, args.product_id, args.quantity), empty_results);
|
||||
|
||||
// We can use static_results::last_insert_id to get the ID of the new line item.
|
||||
auto new_line_item_id = empty_results.last_insert_id();
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::static_results<order_with_items> order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", empty_results);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Created line item: id=" << new_line_item_id << "\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// remove-line-item <line-item-id>: removes an item from an order
|
||||
void operator()(const remove_line_item_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::static_results<empty> empty_results;
|
||||
conn.execute("START TRANSACTION", empty_results);
|
||||
|
||||
// To remove a line item, we require the order to be in a draft status. Get the order to check this
|
||||
// fact.
|
||||
mysql::static_results<order> orders;
|
||||
auto stmt = conn.prepare_statement(R"%(
|
||||
SELECT orders.id, orders.`status`
|
||||
FROM orders
|
||||
JOIN order_items items ON (orders.id = items.order_id)
|
||||
WHERE items.id = ?
|
||||
)%");
|
||||
conn.execute(stmt.bind(args.line_item_id), orders);
|
||||
if (orders.rows().empty())
|
||||
{
|
||||
// The query hasn't matched any row - the supplied line item ID is not valid
|
||||
throw std::runtime_error(
|
||||
"The order item with id=" + std::to_string(args.line_item_id) + " does not exist"
|
||||
);
|
||||
}
|
||||
const order& ord = orders.rows()[0];
|
||||
if (ord.status != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error("The order is not in an editable state");
|
||||
}
|
||||
|
||||
// Remove the line item
|
||||
stmt = conn.prepare_statement("DELETE FROM order_items WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.line_item_id), empty_results);
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::static_results<order_with_items> order_results = get_order_with_items(ord.id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", empty_results);
|
||||
|
||||
// Print results to stdout
|
||||
std::cout << "Removed line item from order\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// checkout-order <order-id>: marks an order as ready for checkout
|
||||
void operator()(const checkout_order_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::static_results<empty> empty_results;
|
||||
conn.execute("START TRANSACTION", empty_results);
|
||||
|
||||
// To checkout an order, we require it to be in a draft status. Check this fact.
|
||||
mysql::statement stmt = conn.prepare_statement("SELECT id, `status` FROM orders WHERE id = ?");
|
||||
mysql::static_results<order> orders;
|
||||
conn.execute(stmt.bind(args.order_id), orders);
|
||||
if (orders.rows().empty())
|
||||
{
|
||||
// No order matched
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (orders.rows()[0].status != "draft")
|
||||
{
|
||||
// The order is no longer editable
|
||||
throw std::runtime_error(
|
||||
"Order with id=" + std::to_string(args.order_id) + " cannot be checked out"
|
||||
);
|
||||
}
|
||||
|
||||
// Update the order status
|
||||
stmt = conn.prepare_statement("UPDATE orders SET `status` = 'pending_payment' WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), empty_results);
|
||||
|
||||
// Calculate the total amount to pay. SUM() returns a DECIMAL, which has a bigger
|
||||
// range than integers. DECIMAL is represented in C++ as a string. We use CAST to obtain
|
||||
// an uint64_t. If the CAST overflows, the max value for uint64_t will be returned.
|
||||
// We will be limiting our orders to USD 1bn, so overflow will be detected.
|
||||
stmt = conn.prepare_statement(R"%(
|
||||
SELECT CAST(
|
||||
IFNULL(SUM(prod.price * item.quantity), 0)
|
||||
AS UNSIGNED
|
||||
)
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = ?;
|
||||
)%");
|
||||
mysql::static_results<std::tuple<std::uint64_t>> amount_results;
|
||||
conn.execute(stmt.bind(args.order_id), amount_results);
|
||||
std::uint64_t total_amount = std::get<0>(amount_results.rows()[0]);
|
||||
|
||||
// Verify that the total amount meets our criteria
|
||||
if (total_amount == 0)
|
||||
{
|
||||
throw std::runtime_error("The order doesn't have any line item");
|
||||
}
|
||||
else if (total_amount > 1000 * 1000 * 100)
|
||||
{
|
||||
throw std::runtime_error("Order amount of " + std::to_string(total_amount) + " exceeds limit");
|
||||
}
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::static_results<order_with_items> order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", empty_results);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Checked out order. The total amount to pay is: " << total_amount / 100.0 << "$\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
|
||||
// complete-order <order-id>: marks an order as completed
|
||||
void operator()(const complete_order_args& args) const
|
||||
{
|
||||
// We will need to run several statements atomically, so we start a transaction.
|
||||
mysql::static_results<empty> empty_results;
|
||||
conn.execute("START TRANSACTION", empty_results);
|
||||
|
||||
// To complete an order, we require it to be in a pending_payment status. Check this fact.
|
||||
auto stmt = conn.prepare_statement("SELECT id, `status` FROM orders WHERE id = ?");
|
||||
mysql::static_results<order> orders;
|
||||
conn.execute(stmt.bind(args.order_id), orders);
|
||||
if (orders.rows().empty())
|
||||
{
|
||||
// Order not found
|
||||
throw std::runtime_error("Order with id=" + std::to_string(args.order_id) + " not found");
|
||||
}
|
||||
else if (orders.rows()[0].status != "pending_payment")
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Order with id=" + std::to_string(args.order_id) + " is not in pending_payment status"
|
||||
);
|
||||
}
|
||||
|
||||
// Update status
|
||||
stmt = conn.prepare_statement("UPDATE orders SET `status` = 'complete' WHERE id = ?");
|
||||
conn.execute(stmt.bind(args.order_id), empty_results);
|
||||
|
||||
// Retrieve the full order details
|
||||
mysql::static_results<order_with_items> order_results = get_order_with_items(args.order_id);
|
||||
|
||||
// We're done - commit the transaction
|
||||
conn.execute("COMMIT", empty_results);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Completed order\n";
|
||||
print_order_with_items(order_results.rows());
|
||||
}
|
||||
};
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
// Parse command line arguments
|
||||
auto args = parse_cmdline_args(argc, argv);
|
||||
|
||||
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Connection params
|
||||
mysql::handshake_params params(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_order_management" // database to use
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(args.host, mysql::default_port_string);
|
||||
|
||||
// TCP and MySQL level connect
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
// Execute the command
|
||||
boost::variant2::visit(visitor{conn}, args.cmd);
|
||||
|
||||
// Close the connection
|
||||
conn.close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
|
||||
// field value that caused the error) and is encoded using to the connection's encoding
|
||||
// (UTF-8 by default). Treat is as untrusted input.
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n'
|
||||
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//]
|
@ -7,22 +7,20 @@
|
||||
|
||||
//[example_stored_procedures
|
||||
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql.hpp>
|
||||
#include <boost/mysql/tcp_ssl.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/variant2/variant.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
/**
|
||||
* This example implements a ver simple command-line order manager
|
||||
* This example implements a very simple command-line order manager
|
||||
* for an online store, using stored procedures. You can find the procedure
|
||||
* definitions in example/db_setup_stored_procedures.sql. Be sure to run this file before the example.
|
||||
* This example assumes you are connecting to a localhost MySQL server.
|
||||
@ -37,207 +35,36 @@
|
||||
* After that, payment would happen through an external system. Once completed, an
|
||||
* order is confirmed, transitioning it to the complete status.
|
||||
* In the real world, flow would be much more complex, but this is enough for an example.
|
||||
*
|
||||
* We'll be using the untyped interface to retrieve results from MySQL.
|
||||
* This makes use of the results, rows_view, row_view and field_view classes.
|
||||
* If you prefer typing your rows statically, you may prefer using the "typed interface",
|
||||
* which uses static_results instead.
|
||||
*/
|
||||
|
||||
using boost::mysql::resultset_view;
|
||||
using boost::mysql::row_view;
|
||||
using boost::mysql::rows_view;
|
||||
using boost::mysql::string_view;
|
||||
// This header contains boilerplate code to parse the command line
|
||||
// arguments into structs. Parsing the command line yields a cmdline_args,
|
||||
// an alias for a boost::variant2::variant holding the command line
|
||||
// arguments for any of the subcommands. We will use it via visit().
|
||||
#include "parse_cmdline.hpp"
|
||||
|
||||
namespace mysql = boost::mysql;
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Our command line tool implements several sub-commands. Each sub-command
|
||||
* has a set of arguments. We define a struct for each sub-command.
|
||||
*/
|
||||
|
||||
struct get_products_args
|
||||
{
|
||||
std::string search;
|
||||
};
|
||||
|
||||
struct create_order_args
|
||||
{
|
||||
};
|
||||
|
||||
struct get_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
struct get_orders_args
|
||||
{
|
||||
};
|
||||
|
||||
struct add_line_item_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
std::int64_t product_id;
|
||||
std::int64_t quantity;
|
||||
};
|
||||
|
||||
struct remove_line_item_args
|
||||
{
|
||||
std::int64_t line_item_id;
|
||||
};
|
||||
|
||||
struct checkout_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
struct complete_order_args
|
||||
{
|
||||
std::int64_t order_id;
|
||||
};
|
||||
|
||||
// A variant type that can represent arguments for any of the sub-commands
|
||||
using any_command = boost::variant2::variant<
|
||||
get_products_args,
|
||||
get_order_args,
|
||||
get_orders_args,
|
||||
create_order_args,
|
||||
add_line_item_args,
|
||||
remove_line_item_args,
|
||||
checkout_order_args,
|
||||
complete_order_args>;
|
||||
|
||||
// In-memory representation of the command-line arguments once parsed.
|
||||
struct cmdline_args
|
||||
{
|
||||
const char* username;
|
||||
const char* password;
|
||||
const char* host;
|
||||
any_command cmd;
|
||||
};
|
||||
|
||||
// Call on error to print usage and exit
|
||||
[[noreturn]] void usage(string_view program_name)
|
||||
{
|
||||
std::cerr << "Usage: " << program_name << " <username> <password> <server-hostname> <command> args...\n"
|
||||
<< "Available commands:\n"
|
||||
" get-products <search-term>\n"
|
||||
" create-order\n"
|
||||
" get-order <order-id>\n"
|
||||
" get-orders\n"
|
||||
" add-line-item <order-id> <product-id> <quantity>\n"
|
||||
" remove-line-item <line-item-id>\n"
|
||||
" checkout-order <order-id>\n"
|
||||
" complete-order <order-id>"
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Helper function to parse a sub-command
|
||||
any_command parse_subcommand(string_view program_name, string_view cmd_name, int argc_rest, char** argv_rest)
|
||||
{
|
||||
if (cmd_name == "get-products")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_products_args{argv_rest[0]};
|
||||
}
|
||||
else if (cmd_name == "create-order")
|
||||
{
|
||||
if (argc_rest != 0)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return create_order_args{};
|
||||
}
|
||||
else if (cmd_name == "get-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else if (cmd_name == "get-orders")
|
||||
{
|
||||
if (argc_rest != 0)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return get_orders_args{};
|
||||
}
|
||||
else if (cmd_name == "add-line-item")
|
||||
{
|
||||
if (argc_rest != 3)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return add_line_item_args{
|
||||
std::stoi(argv_rest[0]),
|
||||
std::stoi(argv_rest[1]),
|
||||
std::stoi(argv_rest[2]),
|
||||
};
|
||||
}
|
||||
else if (cmd_name == "remove-line-item")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return remove_line_item_args{
|
||||
std::stoi(argv_rest[0]),
|
||||
};
|
||||
}
|
||||
else if (cmd_name == "checkout-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return checkout_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else if (cmd_name == "complete-order")
|
||||
{
|
||||
if (argc_rest != 1)
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
return complete_order_args{std::stoi(argv_rest[0])};
|
||||
}
|
||||
else
|
||||
{
|
||||
usage(program_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Parses the entire command line
|
||||
cmdline_args parse_cmdline_args(int argc, char** argv)
|
||||
{
|
||||
if (argc < 5)
|
||||
{
|
||||
usage(argv[0]);
|
||||
}
|
||||
return cmdline_args{
|
||||
argv[1],
|
||||
argv[2],
|
||||
argv[3],
|
||||
parse_subcommand(argv[0], argv[4], argc - 5, argv + 5),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We'll be using the variant using visit().
|
||||
* This visitor executes a sub-command and prints the results to stdout.
|
||||
*/
|
||||
// This visitor executes a sub-command and prints the results to stdout.
|
||||
struct visitor
|
||||
{
|
||||
boost::mysql::tcp_ssl_connection& conn;
|
||||
mysql::tcp_ssl_connection& conn;
|
||||
|
||||
// Prints the details of an order to stdout. An order here is represented as a row
|
||||
static void print_order(row_view order)
|
||||
static void print_order(mysql::row_view order)
|
||||
{
|
||||
std::cout << "Order: id=" << order.at(0) << ", status=" << order.at(1) << '\n';
|
||||
}
|
||||
|
||||
// Prints the details of an order line item, again represented as a row
|
||||
static void print_line_item(row_view item)
|
||||
static void print_line_item(mysql::row_view item)
|
||||
{
|
||||
std::cout << " Line item: id=" << item.at(0) << ", quantity=" << item.at(1)
|
||||
<< ", unit_price=" << item.at(2).as_int64() / 100.0 << "$\n";
|
||||
@ -246,20 +73,23 @@ struct visitor
|
||||
// Procedures that manipulate orders return two resultsets: one describing
|
||||
// the order and another with the line items the order has. Some of them
|
||||
// return only the order resultset. These functions print order details to stdout
|
||||
static void print_order_with_items(resultset_view order_resultset, resultset_view line_items_resultset)
|
||||
static void print_order_with_items(
|
||||
mysql::resultset_view order_resultset,
|
||||
mysql::resultset_view line_items_resultset
|
||||
)
|
||||
{
|
||||
// First resultset: order information. Always a single row
|
||||
print_order(order_resultset.rows().at(0));
|
||||
|
||||
// Second resultset: all order line items
|
||||
rows_view line_items = line_items_resultset.rows();
|
||||
mysql::rows_view line_items = line_items_resultset.rows();
|
||||
if (line_items.empty())
|
||||
{
|
||||
std::cout << "No line items\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (row_view item : line_items)
|
||||
for (mysql::row_view item : line_items)
|
||||
{
|
||||
print_line_item(item);
|
||||
}
|
||||
@ -272,7 +102,7 @@ struct visitor
|
||||
// We need to pass user-supplied params to CALL, so we use a statement
|
||||
auto stmt = conn.prepare_statement("CALL get_products(?)");
|
||||
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.search), result);
|
||||
auto products = result.front();
|
||||
std::cout << "Your search returned the following products:\n";
|
||||
@ -290,7 +120,7 @@ struct visitor
|
||||
void operator()(const create_order_args&) const
|
||||
{
|
||||
// Since create_order doesn't have user-supplied params, we can use a text query
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute("CALL create_order()", result);
|
||||
|
||||
// Print the result to stdout. create_order() returns a resultset for
|
||||
@ -306,7 +136,7 @@ struct visitor
|
||||
auto stmt = conn.prepare_statement("CALL get_order(?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Print the result to stdout. get_order() returns a resultset for
|
||||
@ -321,19 +151,19 @@ struct visitor
|
||||
void operator()(const get_orders_args&) const
|
||||
{
|
||||
// Since get_orders doesn't have user-supplied params, we can use a text query
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute("CALL get_orders()", result);
|
||||
|
||||
// Print results to stdout. get_orders() succeeds even if no order is found.
|
||||
// get_orders() only lists orders, not line items.
|
||||
rows_view orders = result.front().rows();
|
||||
mysql::rows_view orders = result.front().rows();
|
||||
if (orders.empty())
|
||||
{
|
||||
std::cout << "No orders found" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (row_view order : result.front().rows())
|
||||
for (mysql::row_view order : result.front().rows())
|
||||
{
|
||||
print_order(order);
|
||||
}
|
||||
@ -350,7 +180,7 @@ struct visitor
|
||||
|
||||
// We still have to pass a value to the 4th argument, even if it's an OUT parameter.
|
||||
// The value will be ignored, so we can pass nullptr.
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.order_id, args.product_id, args.quantity, nullptr), result);
|
||||
|
||||
// We can use results::out_params() to access the extra resultset containing
|
||||
@ -369,7 +199,7 @@ struct visitor
|
||||
auto stmt = conn.prepare_statement("CALL remove_line_item(?)");
|
||||
|
||||
// Run the procedure
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.line_item_id), result);
|
||||
|
||||
// Print results to stdout
|
||||
@ -385,7 +215,7 @@ struct visitor
|
||||
auto stmt = conn.prepare_statement("CALL checkout_order(?, ?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.order_id, nullptr), result);
|
||||
|
||||
// We can use results::out_params() to access the extra resultset containing
|
||||
@ -404,7 +234,7 @@ struct visitor
|
||||
auto stmt = conn.prepare_statement("CALL complete_order(?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
mysql::results result;
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Print the results to stdout
|
||||
@ -421,20 +251,20 @@ void main_impl(int argc, char** argv)
|
||||
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
boost::mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Connection params
|
||||
boost::mysql::handshake_params params(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_stored_procedures" // database to use
|
||||
mysql::handshake_params params(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_order_management" // database to use
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(args.host, boost::mysql::default_port_string);
|
||||
auto endpoints = resolver.resolve(args.host, mysql::default_port_string);
|
||||
|
||||
// TCP and MySQL level connect
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
@ -454,7 +284,7 @@ int main(int argc, char** argv)
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const boost::mysql::error_with_diagnostics& err)
|
||||
catch (const mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// If a store procedure fails (e.g. because a SIGNAL statement was executed), an error
|
||||
@ -473,4 +303,4 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
||||
//]
|
401
example/order_management/stored_procedures_cpp14.cpp
Normal file
401
example/order_management/stored_procedures_cpp14.cpp
Normal file
@ -0,0 +1,401 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
//[example_stored_procedures_cpp14
|
||||
|
||||
/**
|
||||
* This example implements a very simple command-line order manager
|
||||
* for an online store, using stored procedures. You can find the procedure
|
||||
* definitions in example/db_setup_stored_procedures.sql. Be sure to run this file before the example.
|
||||
* This example assumes you are connecting to a localhost MySQL server.
|
||||
*
|
||||
* The order system is intentionally very simple, and has the following tables:
|
||||
* - products: the list of items our store sells, with price and description.
|
||||
* - orders: the main object. Orders have a status field that can be draft, pending_payment or complete.
|
||||
* - order_items: an order may have 0 to n line items. Each item refers to a single product.
|
||||
*
|
||||
* Orders are created empty, in a draft state. Line items can be added or removed.
|
||||
* Orders are then checked out, which transitions them to pending_payment.
|
||||
* After that, payment would happen through an external system. Once completed, an
|
||||
* order is confirmed, transitioning it to the complete status.
|
||||
* In the real world, flow would be much more complex, but this is enough for an example.
|
||||
*
|
||||
* We'll be using the static interface to retrieve results from MySQL.
|
||||
* This makes use of the static_results<T1, T2...> class template.
|
||||
* To use it, we need to define a set of structs/tuples describing the shape
|
||||
* of our rows. Boost.MySQL will parse the received rows into these types.
|
||||
* The static interface requires C++14 to work.
|
||||
*
|
||||
* Row types may be plain structs or std::tuple's. If we use plain structs, we need
|
||||
* to use BOOST_DESCRIBE_STRUCT on them. This adds the structs the required reflection
|
||||
* data, so Boost.MySQL knows how to parse rows into them.
|
||||
*/
|
||||
|
||||
#include <boost/mysql/error_with_diagnostics.hpp>
|
||||
#include <boost/mysql/static_results.hpp>
|
||||
#include <boost/mysql/tcp_ssl.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/describe/class.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
// This header contains boilerplate code to parse the command line
|
||||
// arguments into structs. Parsing the command line yields a cmdline_args,
|
||||
// an alias for a boost::variant2::variant holding the command line
|
||||
// arguments for any of the subcommands. We will use it via visit().
|
||||
#include "parse_cmdline.hpp"
|
||||
|
||||
// Including any of the static interface headers brings this macro into
|
||||
// scope if the static interface is supported.
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
namespace mysql = boost::mysql;
|
||||
|
||||
namespace {
|
||||
|
||||
// An order retrieved by our system.
|
||||
struct order
|
||||
{
|
||||
// The unique database ID of the object.
|
||||
std::int64_t id;
|
||||
|
||||
// The order status (draft, pending_payment, complete).
|
||||
std::string status;
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(order, (), (id, status))
|
||||
|
||||
// A line item, associated to an order and to a product.
|
||||
// Our queries don't retrieve the order or product ID, so
|
||||
// we don't include them in this struct.
|
||||
struct order_item
|
||||
{
|
||||
// The unique database ID of the object.
|
||||
std::int64_t id;
|
||||
|
||||
// The number of units of this product that the user wants to buy.
|
||||
std::int64_t quantity;
|
||||
|
||||
// The product's unit price, in cents of USD.
|
||||
std::int64_t unit_price;
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(order_item, (), (id, quantity, unit_price))
|
||||
|
||||
// A product, as listed in the store product catalog.
|
||||
struct product
|
||||
{
|
||||
// The unique database ID of the object.
|
||||
std::int64_t id;
|
||||
|
||||
// A short name for the product. Can be used as a title.
|
||||
std::string short_name;
|
||||
|
||||
// The product's description. This field can be NULL in the DB,
|
||||
// so we use boost::optional<T> for it. If you're using C++17 or higher,
|
||||
// you can use std::optional instead.
|
||||
boost::optional<std::string> descr;
|
||||
|
||||
// The product's unit price, in cents of USD.
|
||||
std::int64_t price;
|
||||
};
|
||||
BOOST_DESCRIBE_STRUCT(product, (), (id, short_name, descr, price))
|
||||
|
||||
// An empty row type. This can be used to describe empty resultsets,
|
||||
// like the ones returned by INSERT or CALL.
|
||||
using empty = std::tuple<>;
|
||||
|
||||
// This visitor executes a sub-command and prints the results to stdout.
|
||||
struct visitor
|
||||
{
|
||||
mysql::tcp_ssl_connection& conn;
|
||||
|
||||
// Prints the details of an order to stdout
|
||||
static void print_order(const order& ord)
|
||||
{
|
||||
std::cout << "Order: id=" << ord.id << ", status=" << ord.status << '\n';
|
||||
}
|
||||
|
||||
// Prints the details of an order line item
|
||||
static void print_line_item(const order_item& item)
|
||||
{
|
||||
std::cout << " Line item: id=" << item.id << ", quantity=" << item.quantity
|
||||
<< ", unit_price=" << item.unit_price / 100.0 << "$\n";
|
||||
}
|
||||
|
||||
// Prints an order with its line items to stdout
|
||||
static void print_order_with_items(const order& ord, boost::span<const order_item> items)
|
||||
{
|
||||
print_order(ord);
|
||||
|
||||
if (items.empty())
|
||||
{
|
||||
std::cout << "No line items\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& item : items)
|
||||
{
|
||||
print_line_item(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get-products <search-term>: full text search of the products table.
|
||||
// use this command to search the store for available products
|
||||
void operator()(const get_products_args& args) const
|
||||
{
|
||||
// We need to pass user-supplied params to CALL, so we use a statement
|
||||
auto stmt = conn.prepare_statement("CALL get_products(?)");
|
||||
|
||||
// get_products returns two resultsets:
|
||||
// 1. A collection of products
|
||||
// 2. An empty resultset describing the effects of the CALL statement
|
||||
mysql::static_results<product, empty> products;
|
||||
conn.execute(stmt.bind(args.search), products);
|
||||
|
||||
// Print the results to stdout. By default, rows() returns the rows for the 1st resultset.
|
||||
std::cout << "Your search returned the following products:\n";
|
||||
for (const product& prod : products.rows())
|
||||
{
|
||||
std::cout << "* ID: " << prod.id << '\n'
|
||||
<< " Short name: " << prod.short_name << '\n'
|
||||
<< " Description: " << (prod.descr ? *prod.descr : "") << '\n'
|
||||
<< " Price: " << prod.price / 100.0 << "$" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// create-order: creates a new order. Orders are always created empty. This command
|
||||
// requires no arguments
|
||||
void operator()(const create_order_args&) const
|
||||
{
|
||||
// Since create_order doesn't have user-supplied params, we can use a text query.
|
||||
// create_order returns two resultsets:
|
||||
// 1. The created order. This is always a single row.
|
||||
// 2. An empty resultset describing the effects of the CALL statement
|
||||
mysql::static_results<order, empty> result;
|
||||
conn.execute("CALL create_order()", result);
|
||||
|
||||
// Print the result to stdout. create_order() returns a resultset for
|
||||
// the newly created order, with only 1 row.
|
||||
std::cout << "Created order\n";
|
||||
print_order(result.rows()[0]);
|
||||
}
|
||||
|
||||
// get-order <order-id>: retrieves order details
|
||||
void operator()(const get_order_args& args) const
|
||||
{
|
||||
// The order_id is supplied by the user, so we use a prepared statement
|
||||
auto stmt = conn.prepare_statement("CALL get_order(?)");
|
||||
|
||||
// get_order returns three resultsets:
|
||||
// 1. The retrieved order. This is always a single row.
|
||||
// 2. A collection of line items for this order.
|
||||
// 3. An empty resultset describing the effects of the CALL statement
|
||||
// If the order can't be found, get_order() raises an error using SIGNAL, which will make
|
||||
// execute() fail with an exception.
|
||||
mysql::static_results<order, order_item, empty> result;
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Print the result to stdout.
|
||||
// rows<i>() can be used to access rows for the i-th resultset.
|
||||
// rows() means rows<0>().
|
||||
std::cout << "Retrieved order\n";
|
||||
print_order_with_items(result.rows<0>()[0], result.rows<1>());
|
||||
}
|
||||
|
||||
// get-orders: lists all orders
|
||||
void operator()(const get_orders_args&) const
|
||||
{
|
||||
// Since get_orders doesn't have user-supplied params, we can use a text query
|
||||
// get_orders returns two resultsets:
|
||||
// 1. A collection of orders.
|
||||
// 2. An empty resultset describing the effects of the CALL statement
|
||||
mysql::static_results<order, empty> result;
|
||||
conn.execute("CALL get_orders()", result);
|
||||
|
||||
// Print results to stdout. get_orders() succeeds even if no order is found.
|
||||
// get_orders() only lists orders, not line items.
|
||||
if (result.rows().empty())
|
||||
{
|
||||
std::cout << "No orders found" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const order& ord : result.rows())
|
||||
{
|
||||
print_order(ord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add-line-item <order-id> <product-id> <quantity>: adds a line item to a given order
|
||||
void operator()(const add_line_item_args& args) const
|
||||
{
|
||||
// add_line_item has several user-supplied arguments, so we must use a statement.
|
||||
// The 4th argument is an OUT parameter. If we bind it by passing a ? marker,
|
||||
// we will get an extra resultset with just its value.
|
||||
auto stmt = conn.prepare_statement("CALL add_line_item(?, ?, ?, ?)");
|
||||
|
||||
// add_line_item returns four resultsets:
|
||||
// 1. The affected order. Always a single row.
|
||||
// 2. A collection of line items for the affected order.
|
||||
// 3. An OUT params resultset, containing the ID of the newly created line item. Single row.
|
||||
// MySQL always marks OUT params as nullable.
|
||||
// 4. An empty resultset describing the effects of the CALL statement
|
||||
using out_params_t = std::tuple<boost::optional<std::int64_t>>;
|
||||
mysql::static_results<order, order_item, out_params_t, empty> result;
|
||||
|
||||
// We still have to pass a value to the 4th argument, even if it's an OUT parameter.
|
||||
// The value will be ignored, so we can pass nullptr.
|
||||
conn.execute(stmt.bind(args.order_id, args.product_id, args.quantity, nullptr), result);
|
||||
|
||||
// We can access the OUT param as we access any other resultset
|
||||
auto new_line_item_id = std::get<0>(result.rows<2>()[0]).value();
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Created line item: id=" << new_line_item_id << "\n";
|
||||
print_order_with_items(result.rows<0>()[0], result.rows<1>());
|
||||
}
|
||||
|
||||
// remove-line-item <line-item-id>: removes an item from an order
|
||||
void operator()(const remove_line_item_args& args) const
|
||||
{
|
||||
// remove_line_item has user-supplied parameters, so we use a statement
|
||||
auto stmt = conn.prepare_statement("CALL remove_line_item(?)");
|
||||
|
||||
// remove_line_item returns three resultsets:
|
||||
// 1. The affected order. Always a single row.
|
||||
// 2. A collection of line items for the affected order.
|
||||
// 3. An empty resultset describing the effects of the CALL statement
|
||||
mysql::static_results<order, order_item, empty> result;
|
||||
conn.execute(stmt.bind(args.line_item_id), result);
|
||||
|
||||
// Print results to stdout
|
||||
std::cout << "Removed line item from order\n";
|
||||
print_order_with_items(result.rows<0>()[0], result.rows<1>());
|
||||
}
|
||||
|
||||
// checkout-order <order-id>: marks an order as ready for checkout
|
||||
void operator()(const checkout_order_args& args) const
|
||||
{
|
||||
// checkout_order has user-supplied parameters, so we use a statement.
|
||||
// The 2nd parameter represents the total order amount and is an OUT parameter.
|
||||
auto stmt = conn.prepare_statement("CALL checkout_order(?, ?)");
|
||||
|
||||
// checkout_order returns four resultsets:
|
||||
// 1. The affected order. Always a single row.
|
||||
// 2. A collection of line items for the affected order.
|
||||
// 3. An OUT params resultset, containing the total amount to pay, in USD cents. Single row.
|
||||
// MySQL always marks OUT params as nullable.
|
||||
// 4. An empty resultset describing the effects of the CALL statement
|
||||
using out_params_t = std::tuple<boost::optional<std::int64_t>>;
|
||||
mysql::static_results<order, order_item, out_params_t, empty> result;
|
||||
conn.execute(stmt.bind(args.order_id, nullptr), result);
|
||||
|
||||
// We can access the OUT param as we access any other resultset
|
||||
auto total_amount = std::get<0>(result.rows<2>()[0]).value_or(0);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Checked out order. The total amount to pay is: " << total_amount / 100.0 << "$\n";
|
||||
print_order_with_items(result.rows<0>()[0], result.rows<1>());
|
||||
}
|
||||
|
||||
// complete-order <order-id>: marks an order as completed
|
||||
void operator()(const complete_order_args& args) const
|
||||
{
|
||||
// complete_order has user-supplied parameters, so we use a statement.
|
||||
auto stmt = conn.prepare_statement("CALL complete_order(?)");
|
||||
|
||||
// complete_order returns three resultsets:
|
||||
// 1. The affected order. Always a single row.
|
||||
// 2. A collection of line items for the affected order.
|
||||
// 3. An empty resultset describing the effects of the CALL statement
|
||||
mysql::static_results<order, order_item, empty> result;
|
||||
conn.execute(stmt.bind(args.order_id), result);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Completed order\n";
|
||||
print_order_with_items(result.rows<0>()[0], result.rows<1>());
|
||||
}
|
||||
};
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
// Parse command line arguments
|
||||
auto args = parse_cmdline_args(argc, argv);
|
||||
|
||||
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Connection params
|
||||
mysql::handshake_params params(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_order_management" // database to use
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(args.host, mysql::default_port_string);
|
||||
|
||||
// TCP and MySQL level connect
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
// Execute the command
|
||||
boost::variant2::visit(visitor{conn}, args.cmd);
|
||||
|
||||
// Close the connection
|
||||
conn.close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// If a store procedure fails (e.g. because a SIGNAL statement was executed), an error
|
||||
// like this will be raised.
|
||||
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
|
||||
// field value that caused the error) and is encoded using to the connection's encoding
|
||||
// (UTF-8 by default). Treat is as untrusted input.
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n'
|
||||
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//]
|
@ -1,166 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
//[example_prepared_statements
|
||||
|
||||
#include <boost/mysql.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <tuple>
|
||||
|
||||
#define ASSERT(expr) \
|
||||
if (!(expr)) \
|
||||
{ \
|
||||
std::cerr << "Assertion failed: " #expr << std::endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
double generate_random_payrise()
|
||||
{
|
||||
std::random_device dev;
|
||||
std::default_random_engine eng(dev());
|
||||
std::uniform_real_distribution<> dist(500.0, 3000.0);
|
||||
return dist(eng);
|
||||
}
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
if (argc != 4 && argc != 5)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0]
|
||||
<< " <username> <password> <server-hostname> [employee-first-name]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// The first_name of the employee we will be working with. This
|
||||
// is user-supplied input, and should be treated as untrusted.
|
||||
const char* first_name = argc == 5 ? argv[4] : "Efficient";
|
||||
|
||||
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
boost::mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Connection params
|
||||
boost::mysql::handshake_params params(
|
||||
argv[1], // username
|
||||
argv[2], // password
|
||||
"boost_mysql_examples" // database to use; leave empty or omit the parameter for no
|
||||
// database
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(argv[3], boost::mysql::default_port_string);
|
||||
|
||||
// TCP and MySQL level connect
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
/**
|
||||
* We can tell MySQL to prepare a statement using connection::prepare_statement.
|
||||
* We provide a string SQL statement, which can include any number of parameters,
|
||||
* identified by question marks.
|
||||
*
|
||||
* Prepared statements are stored in the server on a per-connection basis.
|
||||
* Once a connection is closed, all prepared statements for that connection are deallocated.
|
||||
*
|
||||
* The result of prepare_statement is a boost::mysql::statement object, which
|
||||
* is a lightweight handle for the server-side statement.
|
||||
*
|
||||
* We prepare two statements, a SELECT and an UPDATE.
|
||||
*/
|
||||
//[prepared_statements_prepare
|
||||
boost::mysql::statement salary_getter = conn.prepare_statement(
|
||||
"SELECT salary FROM employee WHERE first_name = ?"
|
||||
);
|
||||
//]
|
||||
|
||||
// num_params() returns the number of parameters (question marks)
|
||||
ASSERT(salary_getter.num_params() == 1);
|
||||
|
||||
boost::mysql::statement salary_updater = conn.prepare_statement(
|
||||
"UPDATE employee SET salary = salary + ? WHERE first_name = ?"
|
||||
);
|
||||
ASSERT(salary_updater.num_params() == 2);
|
||||
|
||||
/*
|
||||
* Once a statement has been prepared, it can be executed by calling
|
||||
* connection::execute(). Parameters are provided to statement::bind(),
|
||||
* which creates a bound statement object that can be passed to execute().
|
||||
* Executing a statement yields a results object.
|
||||
*/
|
||||
//[prepared_statements_execute
|
||||
boost::mysql::results select_result, update_result;
|
||||
conn.execute(salary_getter.bind(first_name), select_result);
|
||||
//]
|
||||
|
||||
// First row, first column, cast to double
|
||||
double old_salary = select_result.rows().at(0).at(0).as_double();
|
||||
std::cout << "The salary before the payrise was: " << old_salary << std::endl;
|
||||
|
||||
// Run the update. In this case, we must pass in two parameters.
|
||||
double payrise = generate_random_payrise();
|
||||
conn.execute(salary_updater.bind(payrise, first_name), update_result);
|
||||
ASSERT(update_result.rows().empty()); // an UPDATE never returns rows
|
||||
|
||||
/**
|
||||
* Execute the select again. We can execute a prepared statement
|
||||
* as many times as we want. We do NOT need to call
|
||||
* connection::prepare_statement() again.
|
||||
*/
|
||||
conn.execute(salary_getter.bind(first_name), select_result);
|
||||
double new_salary = select_result.rows().at(0).at(0).as_double();
|
||||
ASSERT(new_salary > old_salary); // Our update took place
|
||||
std::cout << "The salary after the payrise was: " << new_salary << std::endl;
|
||||
|
||||
/**
|
||||
* Close the statements. Closing a statement deallocates it from the server.
|
||||
* Closing statements implies communicating with the server and can thus fail.
|
||||
*
|
||||
* Statements are automatically deallocated once the connection is closed.
|
||||
* If you are re-using connection objects and preparing statements over time,
|
||||
* you should close your statements to prevent excessive resource usage.
|
||||
* If you are not re-using the connections, or are preparing your statements
|
||||
* just once at application startup, there is no need to perform this step.
|
||||
*/
|
||||
conn.close_statement(salary_updater);
|
||||
conn.close_statement(salary_getter);
|
||||
|
||||
// Close the connection
|
||||
conn.close();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const boost::mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
|
||||
// field value that caused the error) and is encoded using to the connection's encoding
|
||||
// (UTF-8 by default). Treat is as untrusted input.
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n'
|
||||
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
@ -29,7 +29,7 @@ class Runner:
|
||||
self._host = host
|
||||
|
||||
def run(self, subcmd: str, *args: str) -> str:
|
||||
cmdline = [self._exe, 'sp_user', 'sp_password', self._host, subcmd, *args]
|
||||
cmdline = [self._exe, 'orders_user', 'orders_password', self._host, subcmd, *args]
|
||||
print(' + ', cmdline)
|
||||
res = run(cmdline, check=True, stdout=PIPE)
|
||||
print(res.stdout.decode())
|
||||
@ -43,7 +43,12 @@ def main():
|
||||
|
||||
runner = Runner(args.executable, args.host)
|
||||
|
||||
runner.run('get-products', 'feast')
|
||||
# Some examples require C++14 and produce an apology for C++11 compilers
|
||||
output = runner.run('get-products', 'feast')
|
||||
if "your compiler doesn't have the required capabilities to run this example" in output:
|
||||
print('Example reported unsupported compiler')
|
||||
exit(0)
|
||||
|
||||
order_id = _parse_order_id(runner.run('create-order'))
|
||||
runner.run('get-orders')
|
||||
line_item_id = _parse_line_item_id(runner.run('add-line-item', order_id, '1', '5'))
|
||||
|
1147
example/snippets.cpp
1147
example/snippets.cpp
File diff suppressed because it is too large
Load Diff
@ -1,141 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
//[example_text_queries
|
||||
|
||||
#include <boost/mysql.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define ASSERT(expr) \
|
||||
if (!(expr)) \
|
||||
{ \
|
||||
std::cerr << "Assertion failed: " #expr << std::endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints an employee to std::cout. An employee here is a boost::mysql::row_view,
|
||||
* which represents a row returned by a SQL query. row_view objects are an ordered
|
||||
* collection of SQL fields, representing each value returned by the query.
|
||||
*
|
||||
* Indexing a row_view yields a boost::mysql::field_view, which is a variant-like
|
||||
* type representing a single value returned by MySQL.
|
||||
*/
|
||||
void print_employee(boost::mysql::row_view employee)
|
||||
{
|
||||
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
|
||||
<< employee.at(1) << "' earns " // last_name (string)
|
||||
<< employee.at(2) << " dollars yearly\n"; // salary (double)
|
||||
}
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
if (argc != 4)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// The I/O context to perform all operations.
|
||||
boost::asio::io_context ctx;
|
||||
|
||||
/**
|
||||
* Connection parameters that tell us how to connect to the MySQL server:
|
||||
* database credentials and schema to use.
|
||||
*/
|
||||
boost::mysql::handshake_params params(
|
||||
argv[1], // username
|
||||
argv[2], // password
|
||||
"boost_mysql_examples" // database to use; leave empty or omit for no database
|
||||
);
|
||||
|
||||
/* We will use SSL in all our examples. To enable SSL, use boost::mysql::tcp_ssl_connection.
|
||||
* MySQL 8+ default is to use an authentication method that requires SSL, so we encourage
|
||||
* you to use SSL connections if you can.
|
||||
*/
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
|
||||
// Represents a single connection over TCP to a MySQL server.
|
||||
boost::mysql::tcp_ssl_connection conn(ctx, ssl_ctx);
|
||||
|
||||
// To establish the connection, we need a TCP endpoint. We have a hostname,
|
||||
// so we need to perform hostname resolution. We create a resolver for this.
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
|
||||
// Invoke the resolver's appropriate function to perform the resolution.
|
||||
const char* hostname = argv[3];
|
||||
auto endpoints = resolver.resolve(hostname, boost::mysql::default_port_string);
|
||||
|
||||
/**
|
||||
* Before using the connection, we have to connect to the server by:
|
||||
* - Establishing the TCP-level session.
|
||||
* - Authenticating to the MySQL server. The SSL handshake is performed as part of this.
|
||||
* connection::connect takes care of both.
|
||||
*/
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
/**
|
||||
* To issue a SQL query to the database server, use tcp_ssl_connection::execute, passing
|
||||
* a string with the SQL to be executed as first parameter. execute returns a results object by lvalue
|
||||
* reference. Resultset objects contain the retrieved rows, among other info. We will get all employees
|
||||
* working for 'High Growth Startup'.
|
||||
*/
|
||||
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
|
||||
boost::mysql::results result;
|
||||
conn.execute(sql, result);
|
||||
|
||||
// We can access the rows using results::rows
|
||||
for (boost::mysql::row_view employee : result.rows())
|
||||
{
|
||||
print_employee(employee);
|
||||
}
|
||||
|
||||
// We can issue any SQL statement, not only SELECTs. In this case, the returned
|
||||
// results will have no fields and no rows
|
||||
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
|
||||
conn.execute(sql, result);
|
||||
ASSERT(result.rows().empty()); // UPDATEs don't retrieve rows
|
||||
|
||||
// Check we have updated our poor intern salary
|
||||
conn.execute("SELECT salary FROM employee WHERE first_name = 'Underpaid'", result);
|
||||
double salary = result.rows().at(0).at(0).as_double();
|
||||
ASSERT(salary == 10000.0);
|
||||
|
||||
// Close the connection. This notifies the MySQL we want to log out
|
||||
// and then closes the underlying socket. This operation implies a network
|
||||
// transfer and thus can fail
|
||||
conn.close();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const boost::mysql::error_with_diagnostics& err)
|
||||
{
|
||||
// Some errors include additional diagnostics, like server-provided error messages.
|
||||
// Security note: diagnostics::server_message may contain user-supplied values (e.g. the
|
||||
// field value that caused the error) and is encoded using to the connection's encoding
|
||||
// (UTF-8 by default). Treat is as untrusted input.
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << '\n'
|
||||
<< "Server diagnostics: " << err.get_diagnostics().server_message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
@ -48,7 +48,21 @@ enum class client_errc : int
|
||||
wrong_num_params,
|
||||
|
||||
/// The connection mandatory SSL, but the server doesn't accept SSL connections.
|
||||
server_doesnt_support_ssl
|
||||
server_doesnt_support_ssl,
|
||||
|
||||
/// The static interface detected a mismatch between your C++ type definitions and what the server
|
||||
/// returned in the query.
|
||||
metadata_check_failed,
|
||||
|
||||
/// The static interface detected a mismatch between the number of row types passed to `static_results`
|
||||
/// or `static_execution_state` and the number of resultsets returned by your query.
|
||||
num_resultsets_mismatch,
|
||||
|
||||
/// The StaticRow type passed to read_some_rows does not correspond to the resultset type being read.
|
||||
row_type_mismatch,
|
||||
|
||||
/// The static interface encountered an error when parsing a field into a C++ data structure.
|
||||
static_row_parsing_error,
|
||||
};
|
||||
|
||||
/// Creates an \ref error_code from a \ref client_errc.
|
||||
|
@ -84,7 +84,7 @@ inline std::ostream& operator<<(std::ostream& os, column_type t)
|
||||
case column_type::set: return os << "set";
|
||||
case column_type::json: return os << "json";
|
||||
case column_type::geometry: return os << "geometry";
|
||||
default: return os << "<unknown field type>";
|
||||
default: return os << "<unknown column type>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,19 +15,17 @@
|
||||
#include <boost/mysql/handshake_params.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/row.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/execution_request.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/rebind_executor.hpp>
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/concepts.hpp>
|
||||
#include <boost/mysql/detail/protocol/protocol_types.hpp>
|
||||
#include <boost/mysql/detail/typing/writable_field_traits.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@ -37,6 +35,10 @@ namespace boost {
|
||||
/// Boost.MySQL library namespace.
|
||||
namespace mysql {
|
||||
|
||||
// Forward declarations
|
||||
template <class... StaticRow>
|
||||
class static_execution_state;
|
||||
|
||||
/**
|
||||
* \brief A connection to a MySQL server.
|
||||
* \details
|
||||
@ -311,6 +313,7 @@ public:
|
||||
* \brief Executes a text query or prepared statement.
|
||||
* \details
|
||||
* Sends `req` to the server for execution and reads the response into `result`.
|
||||
* `result` may be either a \ref results or \ref static_results object.
|
||||
* `req` should may be either a type convertible to \ref string_view containing valid SQL
|
||||
* or a bound prepared statement, obtained by calling \ref statement::bind.
|
||||
* If a string, it must be encoded using the connection's character set.
|
||||
@ -321,12 +324,12 @@ public:
|
||||
* \n
|
||||
* Metadata in `result` will be populated according to `this->meta_mode()`.
|
||||
*/
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void execute(const ExecutionRequest& req, results& result, error_code&, diagnostics&);
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result, error_code&, diagnostics&);
|
||||
|
||||
/// \copydoc execute
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void execute(const ExecutionRequest& req, results& result);
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void execute(const ExecutionRequest& req, ResultsType& result);
|
||||
|
||||
/**
|
||||
* \copydoc execute
|
||||
@ -347,12 +350,13 @@ public:
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_RESULTS_TYPE ResultsType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute(
|
||||
ExecutionRequest&& req,
|
||||
results& result,
|
||||
ResultsType& result,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
@ -367,12 +371,13 @@ public:
|
||||
/// \copydoc async_execute
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_RESULTS_TYPE ResultsType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute(
|
||||
ExecutionRequest&& req,
|
||||
results& result,
|
||||
ResultsType& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
@ -382,6 +387,8 @@ public:
|
||||
* \details
|
||||
* Writes the execution request and reads the initial server response and the column
|
||||
* metadata, but not the generated rows or subsequent resultsets, if any.
|
||||
* `st` may be either an \ref execution_state or \ref static_execution_state object.
|
||||
* \n
|
||||
* After this operation completes, `st` will have
|
||||
* \ref execution_state::meta populated.
|
||||
* Metadata will be populated according to `this->meta_mode()`.
|
||||
@ -395,13 +402,20 @@ public:
|
||||
* If a string, it must be encoded using the connection's character set.
|
||||
* Any string parameters provided to \ref statement::bind should also be encoded
|
||||
* using the connection's character set.
|
||||
* \n
|
||||
* When using the static interface, this function will detect schema mismatches for the first
|
||||
* resultset. Further errors may be detected by \ref read_resultset_head and \ref read_some_rows.
|
||||
*/
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void start_execution(const ExecutionRequest& req, execution_state& st, error_code&, diagnostics&);
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(const ExecutionRequest& req, ExecutionStateType& st, error_code&, diagnostics&);
|
||||
|
||||
/// \copydoc start_execution
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void start_execution(const ExecutionRequest& req, execution_state& st);
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void start_execution(const ExecutionRequest& req, ExecutionStateType& st);
|
||||
|
||||
/**
|
||||
* \copydoc start_execution
|
||||
@ -422,12 +436,13 @@ public:
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_execution(
|
||||
ExecutionRequest&& req,
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
@ -442,12 +457,13 @@ public:
|
||||
/// \copydoc async_start_execution
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_execution(
|
||||
ExecutionRequest&& req,
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
@ -620,7 +636,7 @@ public:
|
||||
* After this operation completes successfully, `result.has_value() == true`.
|
||||
* \n
|
||||
* The statement actual parameters (`params`) are passed as a `std::tuple` of elements.
|
||||
* See the `FieldLikeTuple` concept defition for more info. You should pass exactly as many
|
||||
* See the `WritableFieldTuple` concept defition for more info. You should pass exactly as many
|
||||
* parameters as `this->num_params()`, or the operation will fail with an error.
|
||||
* String parameters should be encoded using the connection's character set.
|
||||
* \n
|
||||
@ -635,11 +651,12 @@ public:
|
||||
* `stmt.valid() == true`
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
void execute_statement(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
results& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
@ -647,9 +664,10 @@ public:
|
||||
|
||||
/// \copydoc execute_statement
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
void execute_statement(const statement& stmt, const FieldLikeTuple& params, results& result);
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
void execute_statement(const statement& stmt, const WritableFieldTuple& params, results& result);
|
||||
|
||||
/**
|
||||
* \copydoc execute_statement
|
||||
@ -663,21 +681,22 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute_statement(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
results& result,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
return async_execute_statement(
|
||||
stmt,
|
||||
std::forward<FieldLikeTuple>(params),
|
||||
std::forward<WritableFieldTuple>(params),
|
||||
result,
|
||||
shared_diag(),
|
||||
std::forward<CompletionToken>(token)
|
||||
@ -686,14 +705,15 @@ public:
|
||||
|
||||
/// \copydoc async_execute_statement
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute_statement(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
results& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
@ -722,24 +742,30 @@ public:
|
||||
* `stmt.valid() == true`
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
void start_statement_execution(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
execution_state& ex,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
/// \copydoc start_statement_execution(const statement&,const FieldLikeTuple&,execution_state&,error_code&,diagnostics&)
|
||||
/// \copydoc start_statement_execution(const statement&,const WritableFieldTuple&,execution_state&,error_code&,diagnostics&)
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
void start_statement_execution(const statement& stmt, const FieldLikeTuple& params, execution_state& st);
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
void start_statement_execution(
|
||||
const statement& stmt,
|
||||
const WritableFieldTuple& params,
|
||||
execution_state& st
|
||||
);
|
||||
|
||||
/**
|
||||
* \copydoc start_statement_execution(const statement&,const FieldLikeTuple&,execution_state&,error_code&,diagnostics&)
|
||||
* \copydoc start_statement_execution(const statement&,const WritableFieldTuple&,execution_state&,error_code&,diagnostics&)
|
||||
* \details
|
||||
* \par Object lifetimes
|
||||
* If `CompletionToken` is deferred (like `use_awaitable`), and `params` contains any reference
|
||||
@ -751,37 +777,39 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_statement_execution(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
execution_state& st,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
return async_start_statement_execution(
|
||||
stmt,
|
||||
std::forward<FieldLikeTuple>(params),
|
||||
std::forward<WritableFieldTuple>(params),
|
||||
st,
|
||||
get_channel().shared_diag(),
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
/// \copydoc async_start_statement_execution(const statement&,FieldLikeTuple&&,execution_state&,CompletionToken&&)
|
||||
/// \copydoc async_start_statement_execution(const statement&,WritableFieldTuple&&,execution_state&,CompletionToken&&)
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
|
||||
class EnableIf =
|
||||
typename std::enable_if<detail::is_writable_field_tuple<WritableFieldTuple>::value>::type>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_statement_execution(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
@ -935,11 +963,11 @@ public:
|
||||
*/
|
||||
rows_view read_some_rows(execution_state& st, error_code& err, diagnostics& info);
|
||||
|
||||
/// \copydoc read_some_rows
|
||||
/// \copydoc read_some_rows(execution_state&,error_code&,diagnostics&)
|
||||
rows_view read_some_rows(execution_state& st);
|
||||
|
||||
/**
|
||||
* \copydoc read_some_rows
|
||||
* \copydoc read_some_rows(execution_state&,error_code&,diagnostics&)
|
||||
* \details
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation is
|
||||
@ -956,7 +984,7 @@ public:
|
||||
return async_read_some_rows(st, shared_diag(), std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/// \copydoc async_read_some_rows
|
||||
/// \copydoc async_read_some_rows(execution_state&,CompletionToken&&)
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
|
||||
@ -966,6 +994,170 @@ public:
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
/**
|
||||
* \brief Reads a batch of rows.
|
||||
* \details
|
||||
* Reads a batch of rows of unspecified size into the storage given by `output`.
|
||||
* At most `output.size()` rows will be read. If the operation represented by `st`
|
||||
* has still rows to read, and `output.size() > 0`, at least one row will be read.
|
||||
* \n
|
||||
* Returns the number of read rows.
|
||||
* \n
|
||||
* If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns
|
||||
* zero.
|
||||
* \n
|
||||
* The number of rows that will be read depends on the input buffer size. The bigger the buffer,
|
||||
* the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s
|
||||
* constructor, using \ref buffer_params::initial_read_size. The buffer may be
|
||||
* grown bigger by other read operations, if required.
|
||||
* \n
|
||||
* Rows read by this function are owning objects, and don't hold any reference to
|
||||
* the connection's internal buffers (contrary what happens with the dynamic interface's counterpart).
|
||||
* \n
|
||||
* `SpanStaticRow` must exactly be one of the types in the `StaticRow` parameter pack.
|
||||
* The type must match the resultset that is currently being processed by `st`. For instance,
|
||||
* given `static_execution_state<T1, T2>`, when reading rows for the second resultset, `SpanStaticRow`
|
||||
* must exactly be `T2`. If this is not the case, a runtime error will be issued.
|
||||
* \n
|
||||
* This function can report schema mismatches.
|
||||
*/
|
||||
template <class SpanStaticRow, class... StaticRow>
|
||||
std::size_t read_some_rows(
|
||||
static_execution_state<StaticRow...>& st,
|
||||
span<SpanStaticRow> output,
|
||||
error_code& err,
|
||||
diagnostics& info
|
||||
);
|
||||
|
||||
/**
|
||||
* \brief Reads a batch of rows.
|
||||
* \details
|
||||
* Reads a batch of rows of unspecified size into the storage given by `output`.
|
||||
* At most `output.size()` rows will be read. If the operation represented by `st`
|
||||
* has still rows to read, and `output.size() > 0`, at least one row will be read.
|
||||
* \n
|
||||
* Returns the number of read rows.
|
||||
* \n
|
||||
* If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns
|
||||
* zero.
|
||||
* \n
|
||||
* The number of rows that will be read depends on the input buffer size. The bigger the buffer,
|
||||
* the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s
|
||||
* constructor, using \ref buffer_params::initial_read_size. The buffer may be
|
||||
* grown bigger by other read operations, if required.
|
||||
* \n
|
||||
* Rows read by this function are owning objects, and don't hold any reference to
|
||||
* the connection's internal buffers (contrary what happens with the dynamic interface's counterpart).
|
||||
* \n
|
||||
* `SpanStaticRow` must exactly be one of the types in the `StaticRow` parameter pack.
|
||||
* The type must match the resultset that is currently being processed by `st`. For instance,
|
||||
* given `static_execution_state<T1, T2>`, when reading rows for the second resultset, `SpanStaticRow`
|
||||
* must exactly be `T2`. If this is not the case, a runtime error will be issued.
|
||||
* \n
|
||||
* This function can report schema mismatches.
|
||||
*/
|
||||
template <class SpanStaticRow, class... StaticRow>
|
||||
std::size_t read_some_rows(static_execution_state<StaticRow...>& st, span<SpanStaticRow> output);
|
||||
|
||||
/**
|
||||
* \brief Reads a batch of rows.
|
||||
* \details
|
||||
* Reads a batch of rows of unspecified size into the storage given by `output`.
|
||||
* At most `output.size()` rows will be read. If the operation represented by `st`
|
||||
* has still rows to read, and `output.size() > 0`, at least one row will be read.
|
||||
* \n
|
||||
* Returns the number of read rows.
|
||||
* \n
|
||||
* If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns
|
||||
* zero.
|
||||
* \n
|
||||
* The number of rows that will be read depends on the input buffer size. The bigger the buffer,
|
||||
* the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s
|
||||
* constructor, using \ref buffer_params::initial_read_size. The buffer may be
|
||||
* grown bigger by other read operations, if required.
|
||||
* \n
|
||||
* Rows read by this function are owning objects, and don't hold any reference to
|
||||
* the connection's internal buffers (contrary what happens with the dynamic interface's counterpart).
|
||||
* \n
|
||||
* `SpanStaticRow` must exactly be one of the types in the `StaticRow` parameter pack.
|
||||
* The type must match the resultset that is currently being processed by `st`. For instance,
|
||||
* given `static_execution_state<T1, T2>`, when reading rows for the second resultset, `SpanStaticRow`
|
||||
* must exactly be `T2`. If this is not the case, a runtime error will be issued.
|
||||
* \n
|
||||
* This function can report schema mismatches.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation is
|
||||
* `void(boost::mysql::error_code, std::size_t)`.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The storage that `output` references must be kept alive until the operation completes.
|
||||
*/
|
||||
template <
|
||||
class SpanStaticRow,
|
||||
class... StaticRow,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t))
|
||||
async_read_some_rows(
|
||||
static_execution_state<StaticRow...>& st,
|
||||
span<SpanStaticRow> output,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
return async_read_some_rows(st, output, shared_diag(), std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Reads a batch of rows.
|
||||
* \details
|
||||
* Reads a batch of rows of unspecified size into the storage given by `output`.
|
||||
* At most `output.size()` rows will be read. If the operation represented by `st`
|
||||
* has still rows to read, and `output.size() > 0`, at least one row will be read.
|
||||
* \n
|
||||
* Returns the number of read rows.
|
||||
* \n
|
||||
* If there are no more rows, or `st.should_read_rows() == false`, this function is a no-op and returns
|
||||
* zero.
|
||||
* \n
|
||||
* The number of rows that will be read depends on the input buffer size. The bigger the buffer,
|
||||
* the greater the batch size (up to a maximum). You can set the initial buffer size in `connection`'s
|
||||
* constructor, using \ref buffer_params::initial_read_size. The buffer may be
|
||||
* grown bigger by other read operations, if required.
|
||||
* \n
|
||||
* Rows read by this function are owning objects, and don't hold any reference to
|
||||
* the connection's internal buffers (contrary what happens with the dynamic interface's counterpart).
|
||||
* \n
|
||||
* `SpanStaticRow` must exactly be one of the types in the `StaticRow` parameter pack.
|
||||
* The type must match the resultset that is currently being processed by `st`. For instance,
|
||||
* given `static_execution_state<T1, T2>`, when reading rows for the second resultset, `SpanStaticRow`
|
||||
* must exactly be `T2`. If this is not the case, a runtime error will be issued.
|
||||
* \n
|
||||
* This function can report schema mismatches.
|
||||
*
|
||||
* \par Handler signature
|
||||
* The handler signature for this operation is
|
||||
* `void(boost::mysql::error_code, std::size_t)`.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The storage that `output` references must be kept alive until the operation completes.
|
||||
*/
|
||||
template <
|
||||
class SpanStaticRow,
|
||||
class... StaticRow,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t))
|
||||
async_read_some_rows(
|
||||
static_execution_state<StaticRow...>& st,
|
||||
span<SpanStaticRow> output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Reads metadata for subsequent resultsets in a multi-resultset operation.
|
||||
* \details
|
||||
@ -976,13 +1168,21 @@ public:
|
||||
* \n
|
||||
* If `st.should_read_head() == false`, this function is a no-op.
|
||||
* \n
|
||||
* `st` may be either an \ref execution_state or \ref static_execution_state object.
|
||||
* \n
|
||||
* This function is only relevant when using multi-function operations with statements
|
||||
* that return more than one resultset.
|
||||
* \n
|
||||
* When using the static interface, this function will detect schema mismatches for the resultset
|
||||
* currently being read. Further errors may be detected by subsequent invocations of this function
|
||||
* and by \ref read_some_rows.
|
||||
*/
|
||||
void read_resultset_head(execution_state& st, error_code& err, diagnostics& info);
|
||||
template <BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void read_resultset_head(ExecutionStateType& st, error_code& err, diagnostics& info);
|
||||
|
||||
/// \copydoc read_resultset_head
|
||||
void read_resultset_head(execution_state& st);
|
||||
template <BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void read_resultset_head(ExecutionStateType& st);
|
||||
|
||||
/**
|
||||
* \copydoc read_resultset_head
|
||||
@ -990,11 +1190,13 @@ public:
|
||||
* The handler signature for this operation is
|
||||
* `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_resultset_head(
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
@ -1002,11 +1204,13 @@ public:
|
||||
}
|
||||
|
||||
/// \copydoc async_read_resultset_head
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_resultset_head(
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_AUXILIAR_ACCESS_FWD_HPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_ACCESS_FWD_HPP
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
@ -16,15 +18,23 @@ namespace detail {
|
||||
// other members of the library, but are not to be used by end users
|
||||
struct connection_access;
|
||||
struct diagnostics_access;
|
||||
struct execution_state_access;
|
||||
struct field_view_access;
|
||||
struct metadata_access;
|
||||
struct results_access;
|
||||
struct resultset_view_access;
|
||||
struct row_view_access;
|
||||
struct rows_view_access;
|
||||
struct statement_access;
|
||||
|
||||
// A generic access struct to enable access to the implementation of any class
|
||||
struct impl_access
|
||||
{
|
||||
template <class T>
|
||||
static decltype(std::declval<T>().impl_)& get_impl(T& obj) noexcept
|
||||
{
|
||||
return obj.impl_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
@ -1,123 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_AUXILIAR_FIELD_TYPE_TRAITS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_FIELD_TYPE_TRAITS_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/void_t.hpp>
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// field_view_forward_iterator
|
||||
template <typename T, typename = void>
|
||||
struct is_field_view_forward_iterator : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
struct is_field_view_forward_iterator<T, void_t<
|
||||
typename std::enable_if<
|
||||
std::is_convertible<
|
||||
typename std::iterator_traits<T>::reference,
|
||||
::boost::mysql::field_view
|
||||
>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
std::forward_iterator_tag,
|
||||
typename std::iterator_traits<T>::iterator_category
|
||||
>::value
|
||||
>::type
|
||||
>> : std::true_type { };
|
||||
// clang-format on
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept field_view_forward_iterator = is_field_view_forward_iterator<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR ::boost::mysql::detail::field_view_forward_iterator
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
// field_like
|
||||
template <class T>
|
||||
using is_field_like = std::is_constructible<field_view, T>;
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept field_like = is_field_like<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_FIELD_LIKE ::boost::mysql::detail::field_like
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_FIELD_LIKE class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
// field_like_tuple
|
||||
template <class... T>
|
||||
struct is_field_like_tuple_impl : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct is_field_like_tuple_impl<std::tuple<T...>>
|
||||
: mp11::mp_all_of<mp11::mp_list<T...>, is_field_like>
|
||||
{
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
struct is_field_like_tuple : is_field_like_tuple_impl<typename std::decay<Tuple>::type>
|
||||
{
|
||||
};
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept field_like_tuple = is_field_like_tuple<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_FIELD_LIKE_TUPLE ::boost::mysql::detail::field_like_tuple
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_FIELD_LIKE_TUPLE class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
// Helpers
|
||||
template <typename T>
|
||||
using enable_if_field_view_forward_iterator = typename std::enable_if<
|
||||
is_field_view_forward_iterator<T>::value>::type;
|
||||
|
||||
template <typename T>
|
||||
using enable_if_field_like_tuple = typename std::enable_if<is_field_like_tuple<T>::value>::type;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -12,30 +12,13 @@
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/string_view_offset.hpp>
|
||||
#include <boost/mysql/impl/field_view.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline bool overlaps(const void* first1, const void* first2, std::size_t size) noexcept
|
||||
{
|
||||
std::greater_equal<const void*> gte;
|
||||
std::less<const void*> lt;
|
||||
const void* last1 = static_cast<const unsigned char*>(first1) + size;
|
||||
const void* last2 = static_cast<const unsigned char*>(first2) + size;
|
||||
return (gte(first1, first2) && lt(first1, last2)) || (gte(last1, first2) && lt(last1, last2));
|
||||
}
|
||||
|
||||
inline void guarded_memcpy(void* to, const void* from, std::size_t size) noexcept
|
||||
{
|
||||
assert(!overlaps(to, from, size));
|
||||
std::memcpy(to, from, size);
|
||||
}
|
||||
|
||||
inline std::size_t get_string_size(field_view f) noexcept
|
||||
{
|
||||
switch (f.kind())
|
||||
@ -51,7 +34,7 @@ inline unsigned char* copy_string(unsigned char* buffer_it, field_view& f) noexc
|
||||
auto str = f.get_string();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_it, str.data(), str.size());
|
||||
std::memcpy(buffer_it, str.data(), str.size());
|
||||
f = field_view(string_view(reinterpret_cast<const char*>(buffer_it), str.size()));
|
||||
buffer_it += str.size();
|
||||
}
|
||||
@ -63,7 +46,7 @@ inline unsigned char* copy_blob(unsigned char* buffer_it, field_view& f) noexcep
|
||||
auto b = f.get_blob();
|
||||
if (!b.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_it, b.data(), b.size());
|
||||
std::memcpy(buffer_it, b.data(), b.size());
|
||||
f = field_view(blob_view(buffer_it, b.size()));
|
||||
buffer_it += b.size();
|
||||
}
|
||||
@ -79,7 +62,7 @@ inline std::size_t copy_string_as_offset(
|
||||
auto str = f.get_string();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_first + offset, str.data(), str.size());
|
||||
std::memcpy(buffer_first + offset, str.data(), str.size());
|
||||
f = field_view_access::construct(string_view_offset(offset, str.size()), false);
|
||||
return str.size();
|
||||
}
|
||||
@ -95,7 +78,7 @@ inline std::size_t copy_blob_as_offset(
|
||||
auto str = f.get_blob();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_first + offset, str.data(), str.size());
|
||||
std::memcpy(buffer_first + offset, str.data(), str.size());
|
||||
f = field_view_access::construct(string_view_offset(offset, str.size()), true);
|
||||
return str.size();
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <boost/mysql/resultset.hpp>
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/results_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
@ -19,7 +19,7 @@ namespace detail {
|
||||
|
||||
class results_iterator
|
||||
{
|
||||
const execution_state_impl* self_{};
|
||||
const results_impl* self_{};
|
||||
std::size_t index_{};
|
||||
|
||||
public:
|
||||
@ -30,10 +30,7 @@ public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
results_iterator() = default;
|
||||
results_iterator(const execution_state_impl* self, std::size_t index) noexcept
|
||||
: self_(self), index_(index)
|
||||
{
|
||||
}
|
||||
results_iterator(const results_impl* self, std::size_t index) noexcept : self_(self), index_(index) {}
|
||||
|
||||
results_iterator& operator++() noexcept
|
||||
{
|
||||
@ -89,7 +86,7 @@ public:
|
||||
bool operator>=(results_iterator rhs) const noexcept { return index_ >= rhs.index_; }
|
||||
|
||||
std::size_t index() const noexcept { return index_; }
|
||||
const execution_state_impl* obj() const noexcept { return self_; }
|
||||
const results_impl* obj() const noexcept { return self_; }
|
||||
};
|
||||
|
||||
inline results_iterator operator+(std::ptrdiff_t n, results_iterator it) noexcept { return it + n; }
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
@ -19,11 +21,11 @@ namespace detail {
|
||||
|
||||
// Adds num_fields default-constructed fields to the vector, return pointer to the first
|
||||
// allocated value. Used to allocate fields before deserialization
|
||||
inline field_view* add_fields(std::vector<field_view>& storage, std::size_t num_fields)
|
||||
inline span<field_view> add_fields(std::vector<field_view>& storage, std::size_t num_fields)
|
||||
{
|
||||
std::size_t old_size = storage.size();
|
||||
storage.resize(old_size + num_fields);
|
||||
return storage.data() + old_size;
|
||||
return span<field_view>(storage.data() + old_size, num_fields);
|
||||
}
|
||||
|
||||
// A field_view vector with strings pointing into a
|
||||
@ -45,7 +47,7 @@ public:
|
||||
inline void assign(const field_view* fields, std::size_t size);
|
||||
|
||||
// Adds new default constructed fields to provide storage to deserialization
|
||||
field_view* add_fields(std::size_t num_fields)
|
||||
span<field_view> add_fields(std::size_t num_fields)
|
||||
{
|
||||
return ::boost::mysql::detail::add_fields(fields_, num_fields);
|
||||
}
|
||||
|
@ -8,8 +8,11 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_CONFIG_HPP
|
||||
#define BOOST_MYSQL_DETAIL_CONFIG_HPP
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Concepts
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<version>)
|
||||
#include <version>
|
||||
@ -19,6 +22,11 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// C++14 conformance
|
||||
#if BOOST_CXX_VERSION >= 201402L
|
||||
#define BOOST_MYSQL_CXX14
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
#endif
|
||||
|
74
include/boost/mysql/detail/execution_processor/concepts.hpp
Normal file
74
include/boost/mysql/detail/execution_processor/concepts.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_CONCEPTS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_CONCEPTS_HPP
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
// Forward decls
|
||||
template <class... StaticRow>
|
||||
class static_execution_state;
|
||||
|
||||
template <class... StaticRow>
|
||||
class static_results;
|
||||
|
||||
class execution_state;
|
||||
class results;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Execution state
|
||||
template <class T>
|
||||
struct is_static_execution_state : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct is_static_execution_state<static_execution_state<T...>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
concept execution_state_type = std::is_same_v<T, execution_state> || is_static_execution_state<T>::value;
|
||||
|
||||
// Results
|
||||
template <class T>
|
||||
struct is_static_results : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct is_static_results<static_results<T...>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
concept results_type = std::is_same_v<T, results> || is_static_results<T>::value;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#define BOOST_MYSQL_EXECUTION_STATE_TYPE ::boost::mysql::detail::execution_state_type
|
||||
#define BOOST_MYSQL_RESULTS_TYPE ::boost::mysql::detail::results_type
|
||||
|
||||
#else
|
||||
|
||||
#define BOOST_MYSQL_EXECUTION_STATE_TYPE class
|
||||
#define BOOST_MYSQL_RESULTS_TYPE class
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -0,0 +1,229 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_PROCESSOR_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_PROCESSOR_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/db_flavor.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// A type-erased reference to be used as the output range for static_execution_state
|
||||
class output_ref
|
||||
{
|
||||
// Pointer to the first element of the span
|
||||
void* data_{};
|
||||
|
||||
// Number of elements in the span
|
||||
std::size_t max_size_{(std::numeric_limits<std::size_t>::max)()};
|
||||
|
||||
// Identifier for the type of elements. Index in the resultset type list
|
||||
std::size_t type_index_{};
|
||||
|
||||
// Offset into the span's data (static_execution_state). Otherwise unused
|
||||
std::size_t offset_{};
|
||||
|
||||
public:
|
||||
constexpr output_ref() noexcept = default;
|
||||
|
||||
template <class T>
|
||||
constexpr output_ref(boost::span<T> span, std::size_t type_index, std::size_t offset = 0) noexcept
|
||||
: data_(span.data()), max_size_(span.size()), type_index_(type_index), offset_(offset)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t max_size() const noexcept { return max_size_; }
|
||||
std::size_t type_index() const noexcept { return type_index_; }
|
||||
std::size_t offset() const noexcept { return offset_; }
|
||||
void set_offset(std::size_t v) noexcept { offset_ = v; }
|
||||
|
||||
template <class T>
|
||||
T& span_element() const noexcept
|
||||
{
|
||||
assert(data_);
|
||||
return static_cast<T*>(data_)[offset_];
|
||||
}
|
||||
};
|
||||
|
||||
class execution_processor
|
||||
{
|
||||
public:
|
||||
virtual ~execution_processor() {}
|
||||
|
||||
void reset(resultset_encoding enc, metadata_mode mode) noexcept
|
||||
{
|
||||
state_ = state_t::reading_first;
|
||||
encoding_ = enc;
|
||||
mode_ = mode;
|
||||
seqnum_ = 0;
|
||||
remaining_meta_ = 0;
|
||||
reset_impl();
|
||||
}
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD error_code on_head_ok_packet(const ok_packet& pack, diagnostics& diag)
|
||||
{
|
||||
assert(is_reading_head());
|
||||
auto err = on_head_ok_packet_impl(pack, diag);
|
||||
set_state_for_ok(pack);
|
||||
return err;
|
||||
}
|
||||
|
||||
void on_num_meta(std::size_t num_columns)
|
||||
{
|
||||
assert(is_reading_head());
|
||||
on_num_meta_impl(num_columns);
|
||||
remaining_meta_ = num_columns;
|
||||
set_state(state_t::reading_metadata);
|
||||
}
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD error_code on_meta(const column_definition_packet& pack, diagnostics& diag)
|
||||
{
|
||||
return on_meta_helper(
|
||||
metadata_access::construct(pack, mode_ == metadata_mode::full),
|
||||
pack.name.value,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
// Exposed for the sake of testing
|
||||
BOOST_ATTRIBUTE_NODISCARD error_code on_meta(metadata&& meta, diagnostics& diag)
|
||||
{
|
||||
std::string field_name = meta.column_name();
|
||||
return on_meta_helper(std::move(meta), field_name, diag);
|
||||
}
|
||||
|
||||
void on_row_batch_start()
|
||||
{
|
||||
assert(is_reading_rows());
|
||||
on_row_batch_start_impl();
|
||||
}
|
||||
|
||||
void on_row_batch_finish() { on_row_batch_finish_impl(); }
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD error_code
|
||||
on_row(deserialization_context ctx, const output_ref& ref, std::vector<field_view>& storage)
|
||||
{
|
||||
assert(is_reading_rows());
|
||||
return on_row_impl(ctx, ref, storage);
|
||||
}
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD error_code on_row_ok_packet(const ok_packet& pack)
|
||||
{
|
||||
assert(is_reading_rows());
|
||||
auto err = on_row_ok_packet_impl(pack);
|
||||
set_state_for_ok(pack);
|
||||
return err;
|
||||
}
|
||||
|
||||
bool is_reading_first() const noexcept { return state_ == state_t::reading_first; }
|
||||
bool is_reading_first_subseq() const noexcept { return state_ == state_t::reading_first_subseq; }
|
||||
bool is_reading_head() const noexcept
|
||||
{
|
||||
return state_ == state_t::reading_first || state_ == state_t::reading_first_subseq;
|
||||
}
|
||||
bool is_reading_meta() const noexcept { return state_ == state_t::reading_metadata; }
|
||||
bool is_reading_rows() const noexcept { return state_ == state_t::reading_rows; }
|
||||
bool is_complete() const noexcept { return state_ == state_t::complete; }
|
||||
|
||||
resultset_encoding encoding() const noexcept { return encoding_; }
|
||||
std::uint8_t& sequence_number() noexcept { return seqnum_; }
|
||||
metadata_mode meta_mode() const noexcept { return mode_; }
|
||||
|
||||
protected:
|
||||
virtual void reset_impl() noexcept = 0;
|
||||
virtual error_code on_head_ok_packet_impl(const ok_packet& pack, diagnostics& diag) = 0;
|
||||
virtual void on_num_meta_impl(std::size_t num_columns) = 0;
|
||||
virtual error_code on_meta_impl(
|
||||
metadata&& meta,
|
||||
string_view column_name,
|
||||
bool is_last,
|
||||
diagnostics& diag
|
||||
) = 0;
|
||||
virtual error_code on_row_ok_packet_impl(const ok_packet& pack) = 0;
|
||||
virtual error_code on_row_impl(
|
||||
deserialization_context ctx,
|
||||
const output_ref& ref,
|
||||
std::vector<field_view>& storage
|
||||
) = 0;
|
||||
virtual void on_row_batch_start_impl() = 0;
|
||||
virtual void on_row_batch_finish_impl() = 0;
|
||||
|
||||
private:
|
||||
enum class state_t
|
||||
{
|
||||
// waiting for 1st packet, for the 1st resultset
|
||||
reading_first,
|
||||
|
||||
// same, but for subsequent resultsets (distiguised to provide a cleaner xp to
|
||||
// the user in (static_)execution_state)
|
||||
reading_first_subseq,
|
||||
|
||||
// waiting for metadata packets
|
||||
reading_metadata,
|
||||
|
||||
// waiting for rows
|
||||
reading_rows,
|
||||
|
||||
// done
|
||||
complete
|
||||
};
|
||||
|
||||
state_t state_{state_t::reading_first};
|
||||
resultset_encoding encoding_{resultset_encoding::text};
|
||||
std::uint8_t seqnum_{};
|
||||
metadata_mode mode_{metadata_mode::minimal};
|
||||
std::size_t remaining_meta_{};
|
||||
|
||||
void set_state(state_t v) noexcept { state_ = v; }
|
||||
|
||||
error_code on_meta_helper(metadata&& meta, string_view column_name, diagnostics& diag)
|
||||
{
|
||||
assert(is_reading_meta());
|
||||
bool is_last = --remaining_meta_ == 0;
|
||||
auto err = on_meta_impl(std::move(meta), column_name, is_last, diag);
|
||||
if (is_last)
|
||||
set_state(state_t::reading_rows);
|
||||
return err;
|
||||
}
|
||||
|
||||
void set_state_for_ok(const ok_packet& pack) noexcept
|
||||
{
|
||||
if (pack.status_flags & SERVER_MORE_RESULTS_EXISTS)
|
||||
{
|
||||
set_state(state_t::reading_first_subseq);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_state(state_t::complete);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -0,0 +1,150 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_STATE_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_EXECUTION_STATE_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class execution_state_impl final : public execution_processor
|
||||
{
|
||||
struct ok_data
|
||||
{
|
||||
bool has_value{false}; // The OK packet information is default constructed, or actual data?
|
||||
std::uint64_t affected_rows{}; // OK packet data
|
||||
std::uint64_t last_insert_id{}; // OK packet data
|
||||
std::uint16_t warnings{}; // OK packet data
|
||||
bool is_out_params{false}; // Does this resultset contain OUT param information?
|
||||
};
|
||||
|
||||
std::vector<metadata> meta_;
|
||||
ok_data eof_data_;
|
||||
std::vector<char> info_;
|
||||
|
||||
void on_new_resultset() noexcept
|
||||
{
|
||||
meta_.clear();
|
||||
eof_data_ = ok_data{};
|
||||
info_.clear();
|
||||
}
|
||||
|
||||
void on_ok_packet_impl(const ok_packet& pack)
|
||||
{
|
||||
eof_data_.has_value = true;
|
||||
eof_data_.affected_rows = pack.affected_rows.value;
|
||||
eof_data_.last_insert_id = pack.last_insert_id.value;
|
||||
eof_data_.warnings = pack.warnings;
|
||||
eof_data_.is_out_params = pack.status_flags & SERVER_PS_OUT_PARAMS;
|
||||
info_.assign(pack.info.value.begin(), pack.info.value.end());
|
||||
}
|
||||
|
||||
void reset_impl() noexcept override final
|
||||
{
|
||||
meta_.clear();
|
||||
eof_data_ = ok_data();
|
||||
info_.clear();
|
||||
}
|
||||
|
||||
error_code on_head_ok_packet_impl(const ok_packet& pack, diagnostics&) override final
|
||||
{
|
||||
on_new_resultset();
|
||||
on_ok_packet_impl(pack);
|
||||
return error_code();
|
||||
}
|
||||
|
||||
void on_num_meta_impl(std::size_t num_columns) override final
|
||||
{
|
||||
on_new_resultset();
|
||||
meta_.reserve(num_columns);
|
||||
}
|
||||
|
||||
error_code on_meta_impl(metadata&& meta, string_view, bool, diagnostics&) override final
|
||||
{
|
||||
meta_.push_back(std::move(meta));
|
||||
return error_code();
|
||||
}
|
||||
|
||||
error_code on_row_impl(deserialization_context ctx, const output_ref&, std::vector<field_view>& fields)
|
||||
override final
|
||||
{
|
||||
// add row storage
|
||||
span<field_view> storage = add_fields(fields, meta_.size());
|
||||
|
||||
// deserialize the row
|
||||
return deserialize_row(encoding(), ctx, meta_, storage);
|
||||
}
|
||||
|
||||
error_code on_row_ok_packet_impl(const ok_packet& pack) override final
|
||||
{
|
||||
on_ok_packet_impl(pack);
|
||||
return error_code();
|
||||
}
|
||||
|
||||
void on_row_batch_start_impl() noexcept override final {}
|
||||
|
||||
void on_row_batch_finish_impl() noexcept override final {}
|
||||
|
||||
public:
|
||||
execution_state_impl() = default;
|
||||
|
||||
metadata_collection_view meta() const noexcept { return meta_; }
|
||||
|
||||
std::uint64_t get_affected_rows() const noexcept
|
||||
{
|
||||
assert(eof_data_.has_value);
|
||||
return eof_data_.affected_rows;
|
||||
}
|
||||
|
||||
std::uint64_t get_last_insert_id() const noexcept
|
||||
{
|
||||
assert(eof_data_.has_value);
|
||||
return eof_data_.last_insert_id;
|
||||
}
|
||||
|
||||
unsigned get_warning_count() const noexcept
|
||||
{
|
||||
assert(eof_data_.has_value);
|
||||
return eof_data_.warnings;
|
||||
}
|
||||
|
||||
string_view get_info() const noexcept
|
||||
{
|
||||
assert(eof_data_.has_value);
|
||||
return string_view(info_.data(), info_.size());
|
||||
}
|
||||
|
||||
bool get_is_out_params() const noexcept
|
||||
{
|
||||
assert(eof_data_.has_value);
|
||||
return eof_data_.is_out_params;
|
||||
}
|
||||
|
||||
execution_state_impl& get_interface() noexcept { return *this; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
304
include/boost/mysql/detail/execution_processor/results_impl.hpp
Normal file
304
include/boost/mysql/detail/execution_processor/results_impl.hpp
Normal file
@ -0,0 +1,304 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_RESULTS_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_RESULTS_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialization_context.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
struct per_resultset_data
|
||||
{
|
||||
std::size_t num_columns{}; // Number of columns this resultset has
|
||||
std::size_t meta_offset{}; // Offset into the vector of metadata
|
||||
std::size_t field_offset; // Offset into the vector of fields (append mode only)
|
||||
std::size_t num_rows{}; // Number of rows this resultset has (append mode only)
|
||||
std::uint64_t affected_rows{}; // OK packet data
|
||||
std::uint64_t last_insert_id{}; // OK packet data
|
||||
std::uint16_t warnings{}; // OK packet data
|
||||
std::size_t info_offset{}; // Offset into the vector of info characters
|
||||
std::size_t info_size{}; // Number of characters that this resultset's info string has
|
||||
bool has_ok_packet_data{false}; // The OK packet information is default constructed, or actual data?
|
||||
bool is_out_params{false}; // Does this resultset contain OUT param information?
|
||||
};
|
||||
|
||||
// A container similar to a vector with SBO. To avoid depending on Boost.Container
|
||||
class resultset_container
|
||||
{
|
||||
bool first_has_data_{false};
|
||||
per_resultset_data first_;
|
||||
std::vector<per_resultset_data> rest_;
|
||||
|
||||
public:
|
||||
resultset_container() = default;
|
||||
std::size_t size() const noexcept { return !first_has_data_ ? 0 : rest_.size() + 1; }
|
||||
bool empty() const noexcept { return !first_has_data_; }
|
||||
void clear() noexcept
|
||||
{
|
||||
first_has_data_ = false;
|
||||
rest_.clear();
|
||||
}
|
||||
per_resultset_data& operator[](std::size_t i) noexcept
|
||||
{
|
||||
return const_cast<per_resultset_data&>(const_cast<const resultset_container&>(*this)[i]);
|
||||
}
|
||||
const per_resultset_data& operator[](std::size_t i) const noexcept
|
||||
{
|
||||
assert(i < size());
|
||||
return i == 0 ? first_ : rest_[i - 1];
|
||||
}
|
||||
per_resultset_data& back() noexcept
|
||||
{
|
||||
return const_cast<per_resultset_data&>(const_cast<const resultset_container&>(*this).back());
|
||||
}
|
||||
const per_resultset_data& back() const noexcept
|
||||
{
|
||||
assert(first_has_data_);
|
||||
return rest_.empty() ? first_ : rest_.back();
|
||||
}
|
||||
per_resultset_data& emplace_back()
|
||||
{
|
||||
if (!first_has_data_)
|
||||
{
|
||||
first_ = per_resultset_data();
|
||||
first_has_data_ = true;
|
||||
return first_;
|
||||
}
|
||||
else
|
||||
{
|
||||
rest_.emplace_back();
|
||||
return rest_.back();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Rows for all resultsets are stored in a single rows_impl object.
|
||||
// - When a row batch is started, we record how many fields we had before the batch.
|
||||
// - When rows are read, fields are allocated in the rows_impl object, then deserialized against
|
||||
// the allocated storage. At this point, strings/blobs point into the connection read buffer.
|
||||
// - When a row batch is finished, we copy strings/blobs into the rows_impl, then transform them
|
||||
// into offsets to allow rows_impl to grow.
|
||||
// - When the final OK packet is received, offsets are transformed back into views.
|
||||
class results_impl final : public execution_processor
|
||||
{
|
||||
public:
|
||||
results_impl() = default;
|
||||
|
||||
row_view get_out_params() const noexcept
|
||||
{
|
||||
assert(is_complete());
|
||||
for (std::size_t i = 0; i < per_result_.size(); ++i)
|
||||
{
|
||||
if (per_result_[i].is_out_params)
|
||||
{
|
||||
auto res = get_rows(i);
|
||||
return res.empty() ? row_view() : res[0];
|
||||
}
|
||||
}
|
||||
return row_view();
|
||||
}
|
||||
|
||||
std::size_t num_resultsets() const noexcept { return per_result_.size(); }
|
||||
|
||||
rows_view get_rows(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = per_result_[index];
|
||||
return rows_view_access::construct(
|
||||
rows_.fields().data() + resultset_data.field_offset,
|
||||
resultset_data.num_rows * resultset_data.num_columns,
|
||||
resultset_data.num_columns
|
||||
);
|
||||
}
|
||||
metadata_collection_view get_meta(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = get_resultset(index);
|
||||
return metadata_collection_view(
|
||||
meta_.data() + resultset_data.meta_offset,
|
||||
resultset_data.num_columns
|
||||
);
|
||||
}
|
||||
|
||||
std::uint64_t get_affected_rows(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset(index).affected_rows;
|
||||
}
|
||||
|
||||
std::uint64_t get_last_insert_id(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset(index).last_insert_id;
|
||||
}
|
||||
|
||||
unsigned get_warning_count(std::size_t index) const noexcept { return get_resultset(index).warnings; }
|
||||
|
||||
string_view get_info(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = get_resultset(index);
|
||||
return string_view(info_.data() + resultset_data.info_offset, resultset_data.info_size);
|
||||
}
|
||||
|
||||
bool get_is_out_params(std::size_t index) const noexcept { return get_resultset(index).is_out_params; }
|
||||
|
||||
results_impl& get_interface() noexcept { return *this; }
|
||||
|
||||
private:
|
||||
// Virtual impls
|
||||
void reset_impl() noexcept override
|
||||
{
|
||||
meta_.clear();
|
||||
per_result_.clear();
|
||||
info_.clear();
|
||||
rows_.clear();
|
||||
num_fields_at_batch_start_ = no_batch;
|
||||
}
|
||||
|
||||
void on_num_meta_impl(std::size_t num_columns) override
|
||||
{
|
||||
auto& resultset_data = add_resultset();
|
||||
meta_.reserve(meta_.size() + num_columns);
|
||||
resultset_data.num_columns = num_columns;
|
||||
}
|
||||
|
||||
error_code on_head_ok_packet_impl(const ok_packet& pack, diagnostics&) override
|
||||
{
|
||||
add_resultset();
|
||||
on_ok_packet_impl(pack);
|
||||
return error_code();
|
||||
}
|
||||
|
||||
error_code on_meta_impl(metadata&& meta, string_view, bool, diagnostics&) override
|
||||
{
|
||||
meta_.push_back(std::move(meta));
|
||||
return error_code();
|
||||
}
|
||||
|
||||
error_code on_row_impl(deserialization_context ctx, const output_ref&, std::vector<field_view>&) override
|
||||
{
|
||||
assert(has_active_batch());
|
||||
|
||||
// add row storage
|
||||
std::size_t num_fields = current_resultset().num_columns;
|
||||
span<field_view> storage = rows_.add_fields(num_fields);
|
||||
++current_resultset().num_rows;
|
||||
|
||||
// deserialize the row
|
||||
auto err = deserialize_row(encoding(), ctx, current_resultset_meta(), storage);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return error_code();
|
||||
}
|
||||
|
||||
error_code on_row_ok_packet_impl(const ok_packet& pack) override
|
||||
{
|
||||
on_ok_packet_impl(pack);
|
||||
return error_code();
|
||||
}
|
||||
|
||||
void on_row_batch_start_impl() override final
|
||||
{
|
||||
assert(!has_active_batch());
|
||||
num_fields_at_batch_start_ = rows_.fields().size();
|
||||
}
|
||||
|
||||
void on_row_batch_finish_impl() override final { finish_batch(); }
|
||||
|
||||
// Data
|
||||
std::vector<metadata> meta_;
|
||||
resultset_container per_result_;
|
||||
std::vector<char> info_;
|
||||
row_impl rows_;
|
||||
std::size_t num_fields_at_batch_start_{no_batch};
|
||||
|
||||
// Auxiliar
|
||||
static constexpr std::size_t no_batch = std::size_t(-1);
|
||||
|
||||
bool has_active_batch() const noexcept { return num_fields_at_batch_start_ != no_batch; }
|
||||
|
||||
void finish_batch()
|
||||
{
|
||||
if (has_active_batch())
|
||||
{
|
||||
rows_.copy_strings_as_offsets(
|
||||
num_fields_at_batch_start_,
|
||||
rows_.fields().size() - num_fields_at_batch_start_
|
||||
);
|
||||
num_fields_at_batch_start_ = no_batch;
|
||||
}
|
||||
}
|
||||
|
||||
per_resultset_data& current_resultset() noexcept
|
||||
{
|
||||
assert(!per_result_.empty());
|
||||
return per_result_.back();
|
||||
}
|
||||
|
||||
const per_resultset_data& current_resultset() const noexcept
|
||||
{
|
||||
assert(!per_result_.empty());
|
||||
return per_result_.back();
|
||||
}
|
||||
|
||||
per_resultset_data& add_resultset()
|
||||
{
|
||||
// Allocate a new per-resultset object
|
||||
auto& resultset_data = per_result_.emplace_back();
|
||||
resultset_data.meta_offset = meta_.size();
|
||||
resultset_data.field_offset = rows_.fields().size();
|
||||
resultset_data.info_offset = info_.size();
|
||||
return resultset_data;
|
||||
}
|
||||
|
||||
void on_ok_packet_impl(const ok_packet& pack)
|
||||
{
|
||||
auto& resultset_data = current_resultset();
|
||||
resultset_data.affected_rows = pack.affected_rows.value;
|
||||
resultset_data.last_insert_id = pack.last_insert_id.value;
|
||||
resultset_data.warnings = pack.warnings;
|
||||
resultset_data.info_size = pack.info.value.size();
|
||||
resultset_data.has_ok_packet_data = true;
|
||||
resultset_data.is_out_params = pack.status_flags & SERVER_PS_OUT_PARAMS;
|
||||
info_.insert(info_.end(), pack.info.value.begin(), pack.info.value.end());
|
||||
bool is_last = !(pack.status_flags & SERVER_MORE_RESULTS_EXISTS);
|
||||
if (is_last)
|
||||
{
|
||||
finish_batch();
|
||||
rows_.offsets_to_string_views();
|
||||
}
|
||||
}
|
||||
|
||||
const per_resultset_data& get_resultset(std::size_t index) const noexcept
|
||||
{
|
||||
assert(index < per_result_.size());
|
||||
return per_result_[index];
|
||||
}
|
||||
|
||||
metadata_collection_view current_resultset_meta() const noexcept
|
||||
{
|
||||
return get_meta(per_result_.size() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -0,0 +1,355 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_EXECUTION_STATE_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_EXECUTION_STATE_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/typing/get_type_index.hpp>
|
||||
#include <boost/mysql/detail/typing/pos_map.hpp>
|
||||
#include <boost/mysql/detail/typing/row_traits.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
using execst_parse_fn_t =
|
||||
error_code (*)(span<const std::size_t> pos_map, span<const field_view> from, const output_ref& ref);
|
||||
|
||||
struct execst_resultset_descriptor
|
||||
{
|
||||
std::size_t num_columns;
|
||||
name_table_t name_table;
|
||||
meta_check_fn_t meta_check;
|
||||
execst_parse_fn_t parse_fn;
|
||||
std::size_t type_index;
|
||||
};
|
||||
|
||||
class execst_external_data
|
||||
{
|
||||
public:
|
||||
struct ptr_data
|
||||
{
|
||||
std::size_t* pos_map;
|
||||
};
|
||||
|
||||
execst_external_data(span<const execst_resultset_descriptor> desc, ptr_data ptr) noexcept
|
||||
: desc_(desc), ptr_(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t num_resultsets() const noexcept { return desc_.size(); }
|
||||
std::size_t num_columns(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].num_columns;
|
||||
}
|
||||
name_table_t name_table(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].name_table;
|
||||
}
|
||||
meta_check_fn_t meta_check_fn(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].meta_check;
|
||||
}
|
||||
execst_parse_fn_t parse_fn(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].parse_fn;
|
||||
}
|
||||
std::size_t type_index(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].type_index;
|
||||
}
|
||||
span<std::size_t> pos_map(std::size_t idx) const noexcept
|
||||
{
|
||||
return span<std::size_t>(ptr_.pos_map, num_columns(idx));
|
||||
}
|
||||
|
||||
void set_pointers(ptr_data ptr) noexcept { ptr_ = ptr; }
|
||||
|
||||
private:
|
||||
span<const execst_resultset_descriptor> desc_;
|
||||
ptr_data ptr_;
|
||||
};
|
||||
|
||||
class static_execution_state_erased_impl final : public execution_processor
|
||||
{
|
||||
public:
|
||||
static_execution_state_erased_impl(execst_external_data ext) noexcept : ext_(ext) {}
|
||||
|
||||
execst_external_data& ext_data() noexcept { return ext_; }
|
||||
|
||||
metadata_collection_view meta() const noexcept { return meta_; }
|
||||
|
||||
std::uint64_t get_affected_rows() const noexcept
|
||||
{
|
||||
assert(ok_data_.has_value);
|
||||
return ok_data_.affected_rows;
|
||||
}
|
||||
|
||||
std::uint64_t get_last_insert_id() const noexcept
|
||||
{
|
||||
assert(ok_data_.has_value);
|
||||
return ok_data_.last_insert_id;
|
||||
}
|
||||
|
||||
unsigned get_warning_count() const noexcept
|
||||
{
|
||||
assert(ok_data_.has_value);
|
||||
return ok_data_.warnings;
|
||||
}
|
||||
|
||||
string_view get_info() const noexcept
|
||||
{
|
||||
assert(ok_data_.has_value);
|
||||
return string_view(info_.data(), info_.size());
|
||||
}
|
||||
|
||||
bool get_is_out_params() const noexcept
|
||||
{
|
||||
assert(ok_data_.has_value);
|
||||
return ok_data_.is_out_params;
|
||||
}
|
||||
|
||||
private:
|
||||
// Data
|
||||
struct ok_packet_data
|
||||
{
|
||||
bool has_value{false}; // The OK packet information is default constructed, or actual data?
|
||||
std::uint64_t affected_rows{}; // OK packet data
|
||||
std::uint64_t last_insert_id{}; // OK packet data
|
||||
std::uint16_t warnings{}; // OK packet data
|
||||
bool is_out_params{false}; // Does this resultset contain OUT param information?
|
||||
};
|
||||
|
||||
execst_external_data ext_;
|
||||
std::size_t resultset_index_{};
|
||||
ok_packet_data ok_data_;
|
||||
std::vector<char> info_;
|
||||
std::vector<metadata> meta_;
|
||||
|
||||
// Virtual impls
|
||||
void reset_impl() noexcept override final
|
||||
{
|
||||
resultset_index_ = 0;
|
||||
ok_data_ = ok_packet_data();
|
||||
info_.clear();
|
||||
meta_.clear();
|
||||
}
|
||||
|
||||
error_code on_head_ok_packet_impl(const ok_packet& pack, diagnostics& diag) override final
|
||||
{
|
||||
on_new_resultset();
|
||||
auto err = on_ok_packet_impl(pack);
|
||||
if (err)
|
||||
return err;
|
||||
return meta_check(diag);
|
||||
}
|
||||
|
||||
void on_num_meta_impl(std::size_t num_columns) override final
|
||||
{
|
||||
on_new_resultset();
|
||||
meta_.reserve(num_columns);
|
||||
}
|
||||
|
||||
error_code on_meta_impl(metadata&& meta, string_view field_name, bool is_last, diagnostics& diag)
|
||||
override final
|
||||
{
|
||||
std::size_t meta_index = meta_.size();
|
||||
|
||||
// Store the object
|
||||
meta_.push_back(std::move(meta));
|
||||
|
||||
// Record its position
|
||||
pos_map_add_field(current_pos_map(), current_name_table(), meta_index, field_name);
|
||||
|
||||
return is_last ? meta_check(diag) : error_code();
|
||||
}
|
||||
|
||||
error_code on_row_impl(
|
||||
deserialization_context ctx,
|
||||
const output_ref& ref,
|
||||
std::vector<field_view>& fields
|
||||
) override final
|
||||
{
|
||||
// check output
|
||||
if (ref.type_index() != ext_.type_index(resultset_index_ - 1))
|
||||
return client_errc::row_type_mismatch;
|
||||
|
||||
// Allocate temporary space
|
||||
fields.clear();
|
||||
span<field_view> storage = add_fields(fields, meta_.size());
|
||||
|
||||
// deserialize the row
|
||||
auto err = deserialize_row(encoding(), ctx, meta_, storage);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// parse it into the output ref
|
||||
err = ext_.parse_fn(resultset_index_ - 1)(current_pos_map(), storage, ref);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return error_code();
|
||||
}
|
||||
|
||||
error_code on_row_ok_packet_impl(const ok_packet& pack) override final { return on_ok_packet_impl(pack); }
|
||||
|
||||
void on_row_batch_start_impl() noexcept override final {}
|
||||
|
||||
void on_row_batch_finish_impl() noexcept override final {}
|
||||
|
||||
// Auxiliar
|
||||
name_table_t current_name_table() const noexcept { return ext_.name_table(resultset_index_ - 1); }
|
||||
span<std::size_t> current_pos_map() noexcept { return ext_.pos_map(resultset_index_ - 1); }
|
||||
span<const std::size_t> current_pos_map() const noexcept { return ext_.pos_map(resultset_index_ - 1); }
|
||||
|
||||
error_code meta_check(diagnostics& diag) const
|
||||
{
|
||||
return ext_.meta_check_fn(resultset_index_ - 1)(current_pos_map(), meta_, diag);
|
||||
}
|
||||
|
||||
void on_new_resultset() noexcept
|
||||
{
|
||||
++resultset_index_;
|
||||
ok_data_ = ok_packet_data{};
|
||||
info_.clear();
|
||||
meta_.clear();
|
||||
pos_map_reset(current_pos_map());
|
||||
}
|
||||
|
||||
error_code on_ok_packet_impl(const ok_packet& pack)
|
||||
{
|
||||
ok_data_.has_value = true;
|
||||
ok_data_.affected_rows = pack.affected_rows.value;
|
||||
ok_data_.last_insert_id = pack.last_insert_id.value;
|
||||
ok_data_.warnings = pack.warnings;
|
||||
ok_data_.is_out_params = pack.status_flags & SERVER_PS_OUT_PARAMS;
|
||||
info_.assign(pack.info.value.begin(), pack.info.value.end());
|
||||
bool should_be_last = resultset_index_ == ext_.num_resultsets();
|
||||
bool is_last = !(pack.status_flags & SERVER_MORE_RESULTS_EXISTS);
|
||||
return should_be_last == is_last ? error_code() : client_errc::num_resultsets_mismatch;
|
||||
}
|
||||
};
|
||||
|
||||
template <class StaticRow>
|
||||
static error_code execst_parse_fn(
|
||||
span<const std::size_t> pos_map,
|
||||
span<const field_view> from,
|
||||
const output_ref& ref
|
||||
)
|
||||
{
|
||||
return parse(pos_map, from, ref.span_element<StaticRow>());
|
||||
}
|
||||
|
||||
template <class... StaticRow>
|
||||
constexpr std::array<execst_resultset_descriptor, sizeof...(StaticRow)> create_execst_resultset_descriptors()
|
||||
{
|
||||
return {{{
|
||||
get_row_size<StaticRow>(),
|
||||
get_row_name_table<StaticRow>(),
|
||||
&meta_check<StaticRow>,
|
||||
&execst_parse_fn<StaticRow>,
|
||||
get_type_index<StaticRow, StaticRow...>(),
|
||||
}...}};
|
||||
}
|
||||
|
||||
template <class... StaticRow>
|
||||
constexpr std::array<execst_resultset_descriptor, sizeof...(StaticRow)>
|
||||
execst_resultset_descriptor_table = create_execst_resultset_descriptors<StaticRow...>();
|
||||
|
||||
template <BOOST_MYSQL_STATIC_ROW... StaticRow>
|
||||
class static_execution_state_impl
|
||||
{
|
||||
// Storage for our data, which requires knowing the template args
|
||||
struct
|
||||
{
|
||||
std::array<std::size_t, max_num_columns<StaticRow...>> pos_map{};
|
||||
} data_;
|
||||
|
||||
// The type-erased impl, that will use pointers to the above storage
|
||||
static_execution_state_erased_impl impl_;
|
||||
|
||||
execst_external_data::ptr_data ptr_data() noexcept
|
||||
{
|
||||
return {
|
||||
data_.pos_map.data(),
|
||||
};
|
||||
}
|
||||
|
||||
void set_pointers() noexcept { impl_.ext_data().set_pointers(ptr_data()); }
|
||||
|
||||
public:
|
||||
static_execution_state_impl() noexcept
|
||||
: impl_({execst_resultset_descriptor_table<StaticRow...>, ptr_data()})
|
||||
{
|
||||
}
|
||||
|
||||
static_execution_state_impl(const static_execution_state_impl& rhs) : data_(rhs.data_), impl_(rhs.impl_)
|
||||
{
|
||||
set_pointers();
|
||||
}
|
||||
|
||||
static_execution_state_impl(static_execution_state_impl&& rhs) noexcept
|
||||
: data_(std::move(rhs.data_)), impl_(std::move(rhs.impl_))
|
||||
{
|
||||
set_pointers();
|
||||
}
|
||||
|
||||
static_execution_state_impl& operator=(const static_execution_state_impl& rhs)
|
||||
{
|
||||
data_ = rhs.data_;
|
||||
impl_ = rhs.impl_;
|
||||
set_pointers();
|
||||
return *this;
|
||||
}
|
||||
|
||||
static_execution_state_impl& operator=(static_execution_state_impl&& rhs)
|
||||
{
|
||||
data_ = std::move(rhs.data_);
|
||||
impl_ = std::move(rhs.impl_);
|
||||
set_pointers();
|
||||
return *this;
|
||||
}
|
||||
|
||||
~static_execution_state_impl() = default;
|
||||
|
||||
const static_execution_state_erased_impl& get_interface() const noexcept { return impl_; }
|
||||
static_execution_state_erased_impl& get_interface() noexcept { return impl_; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_MYSQL_CXX14
|
||||
|
||||
#endif
|
@ -0,0 +1,409 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_RESULTS_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_EXECUTION_PROCESSOR_STATIC_RESULTS_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialization_context.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/typing/pos_map.hpp>
|
||||
#include <boost/mysql/detail/typing/readable_field_traits.hpp>
|
||||
#include <boost/mysql/detail/typing/row_traits.hpp>
|
||||
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
using results_reset_fn_t = void (*)(void*);
|
||||
using results_parse_fn_t =
|
||||
error_code (*)(span<const std::size_t> pos_map, span<const field_view> from, void* to);
|
||||
|
||||
struct results_resultset_descriptor
|
||||
{
|
||||
std::size_t num_columns;
|
||||
name_table_t name_table;
|
||||
meta_check_fn_t meta_check;
|
||||
results_parse_fn_t parse_fn;
|
||||
};
|
||||
|
||||
struct static_per_resultset_data
|
||||
{
|
||||
std::size_t meta_offset{};
|
||||
std::size_t meta_size{};
|
||||
std::size_t info_offset{};
|
||||
std::size_t info_size{};
|
||||
bool has_ok_packet_data{false}; // The OK packet information is default constructed, or actual data?
|
||||
std::uint64_t affected_rows{}; // OK packet data
|
||||
std::uint64_t last_insert_id{}; // OK packet data
|
||||
std::uint16_t warnings{}; // OK packet data
|
||||
bool is_out_params{false}; // Does this resultset contain OUT param information?
|
||||
};
|
||||
|
||||
class results_external_data
|
||||
{
|
||||
public:
|
||||
struct ptr_data
|
||||
{
|
||||
void* rows;
|
||||
std::size_t* pos_map;
|
||||
static_per_resultset_data* per_resultset;
|
||||
};
|
||||
|
||||
results_external_data(
|
||||
span<const results_resultset_descriptor> desc,
|
||||
results_reset_fn_t reset,
|
||||
ptr_data ptr
|
||||
) noexcept
|
||||
: desc_(desc), reset_(reset), ptr_(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
void set_pointers(ptr_data ptr) noexcept { ptr_ = ptr; }
|
||||
|
||||
std::size_t num_resultsets() const noexcept { return desc_.size(); }
|
||||
std::size_t num_columns(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].num_columns;
|
||||
}
|
||||
name_table_t name_table(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].name_table;
|
||||
}
|
||||
meta_check_fn_t meta_check_fn(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].meta_check;
|
||||
}
|
||||
results_parse_fn_t parse_fn(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return desc_[idx].parse_fn;
|
||||
}
|
||||
results_reset_fn_t reset_fn() const noexcept { return reset_; }
|
||||
void* rows() const noexcept { return ptr_.rows; }
|
||||
span<std::size_t> pos_map(std::size_t idx) const noexcept
|
||||
{
|
||||
return span<std::size_t>(ptr_.pos_map, num_columns(idx));
|
||||
}
|
||||
static_per_resultset_data& per_result(std::size_t idx) const noexcept
|
||||
{
|
||||
assert(idx < num_resultsets());
|
||||
return ptr_.per_resultset[idx];
|
||||
}
|
||||
|
||||
private:
|
||||
span<const results_resultset_descriptor> desc_;
|
||||
results_reset_fn_t reset_;
|
||||
ptr_data ptr_;
|
||||
};
|
||||
|
||||
class static_results_erased_impl final : public execution_processor
|
||||
{
|
||||
public:
|
||||
static_results_erased_impl(results_external_data ext) noexcept : ext_(ext) {}
|
||||
|
||||
results_external_data& ext_data() noexcept { return ext_; }
|
||||
|
||||
metadata_collection_view get_meta(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = ext_.per_result(index);
|
||||
return metadata_collection_view(meta_.data() + resultset_data.meta_offset, resultset_data.meta_size);
|
||||
}
|
||||
|
||||
std::uint64_t get_affected_rows(std::size_t index) const noexcept
|
||||
{
|
||||
return ext_.per_result(index).affected_rows;
|
||||
}
|
||||
|
||||
std::uint64_t get_last_insert_id(std::size_t index) const noexcept
|
||||
{
|
||||
return ext_.per_result(index).last_insert_id;
|
||||
}
|
||||
|
||||
unsigned get_warning_count(std::size_t index) const noexcept { return ext_.per_result(index).warnings; }
|
||||
|
||||
string_view get_info(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = ext_.per_result(index);
|
||||
return string_view(info_.data() + resultset_data.info_offset, resultset_data.info_size);
|
||||
}
|
||||
|
||||
bool get_is_out_params(std::size_t index) const noexcept { return ext_.per_result(index).is_out_params; }
|
||||
|
||||
private:
|
||||
// Virtual implementations
|
||||
void reset_impl() noexcept override
|
||||
{
|
||||
ext_.reset_fn()(ext_.rows());
|
||||
info_.clear();
|
||||
meta_.clear();
|
||||
resultset_index_ = 0;
|
||||
}
|
||||
|
||||
error_code on_head_ok_packet_impl(const ok_packet& pack, diagnostics& diag) override final
|
||||
{
|
||||
add_resultset();
|
||||
auto err = on_ok_packet_impl(pack);
|
||||
if (err)
|
||||
return err;
|
||||
return meta_check(diag);
|
||||
}
|
||||
|
||||
void on_num_meta_impl(std::size_t num_columns) override final
|
||||
{
|
||||
auto& resultset_data = add_resultset();
|
||||
meta_.reserve(meta_.size() + num_columns);
|
||||
resultset_data.meta_size = num_columns;
|
||||
}
|
||||
|
||||
error_code on_meta_impl(metadata&& meta, string_view field_name, bool is_last, diagnostics& diag)
|
||||
override final
|
||||
{
|
||||
std::size_t meta_index = meta_.size() - current_resultset().meta_offset;
|
||||
|
||||
// Store the new object
|
||||
meta_.push_back(std::move(meta));
|
||||
|
||||
// Fill the pos map entry for this field, if any
|
||||
pos_map_add_field(current_pos_map(), current_name_table(), meta_index, field_name);
|
||||
|
||||
return is_last ? meta_check(diag) : error_code();
|
||||
}
|
||||
|
||||
error_code on_row_impl(deserialization_context ctx, const output_ref&, std::vector<field_view>& fields)
|
||||
override final
|
||||
{
|
||||
auto meta = current_resultset_meta();
|
||||
|
||||
// Allocate temporary storage
|
||||
fields.clear();
|
||||
span<field_view> storage = add_fields(fields, meta.size());
|
||||
|
||||
// deserialize the row
|
||||
auto err = deserialize_row(encoding(), ctx, meta, storage);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// parse it against the appropriate tuple element
|
||||
return ext_.parse_fn(resultset_index_ - 1)(current_pos_map(), storage, ext_.rows());
|
||||
}
|
||||
|
||||
error_code on_row_ok_packet_impl(const ok_packet& pack) override final { return on_ok_packet_impl(pack); }
|
||||
|
||||
void on_row_batch_start_impl() override final {}
|
||||
void on_row_batch_finish_impl() override final {}
|
||||
|
||||
// Data
|
||||
results_external_data ext_;
|
||||
std::vector<metadata> meta_;
|
||||
std::vector<char> info_;
|
||||
std::size_t resultset_index_{0};
|
||||
|
||||
// Helpers
|
||||
span<std::size_t> current_pos_map() noexcept { return ext_.pos_map(resultset_index_ - 1); }
|
||||
span<const std::size_t> current_pos_map() const noexcept { return ext_.pos_map(resultset_index_ - 1); }
|
||||
name_table_t current_name_table() const noexcept { return ext_.name_table(resultset_index_ - 1); }
|
||||
static_per_resultset_data& current_resultset() noexcept { return ext_.per_result(resultset_index_ - 1); }
|
||||
metadata_collection_view current_resultset_meta() const noexcept
|
||||
{
|
||||
return get_meta(resultset_index_ - 1);
|
||||
}
|
||||
|
||||
static_per_resultset_data& add_resultset()
|
||||
{
|
||||
++resultset_index_;
|
||||
auto& resultset_data = current_resultset();
|
||||
resultset_data = static_per_resultset_data();
|
||||
resultset_data.meta_offset = meta_.size();
|
||||
resultset_data.info_offset = info_.size();
|
||||
pos_map_reset(current_pos_map());
|
||||
return resultset_data;
|
||||
}
|
||||
|
||||
error_code on_ok_packet_impl(const ok_packet& pack)
|
||||
{
|
||||
auto& resultset_data = current_resultset();
|
||||
resultset_data.affected_rows = pack.affected_rows.value;
|
||||
resultset_data.last_insert_id = pack.last_insert_id.value;
|
||||
resultset_data.warnings = pack.warnings;
|
||||
resultset_data.info_size = pack.info.value.size();
|
||||
resultset_data.has_ok_packet_data = true;
|
||||
resultset_data.is_out_params = pack.status_flags & SERVER_PS_OUT_PARAMS;
|
||||
info_.insert(info_.end(), pack.info.value.begin(), pack.info.value.end());
|
||||
bool should_be_last = resultset_index_ == ext_.num_resultsets();
|
||||
bool is_last = !(pack.status_flags & SERVER_MORE_RESULTS_EXISTS);
|
||||
return should_be_last == is_last ? error_code() : client_errc::num_resultsets_mismatch;
|
||||
}
|
||||
|
||||
error_code meta_check(diagnostics& diag) const
|
||||
{
|
||||
return ext_.meta_check_fn(resultset_index_ - 1)(current_pos_map(), current_resultset_meta(), diag);
|
||||
}
|
||||
};
|
||||
|
||||
template <class... StaticRow>
|
||||
using results_rows_t = std::tuple<std::vector<StaticRow>...>;
|
||||
|
||||
template <class... StaticRow>
|
||||
struct results_fns
|
||||
{
|
||||
using rows_t = results_rows_t<StaticRow...>;
|
||||
|
||||
struct reset_fn
|
||||
{
|
||||
rows_t& obj;
|
||||
|
||||
template <std::size_t I>
|
||||
void operator()(boost::mp11::mp_size_t<I>) const noexcept
|
||||
{
|
||||
std::get<I>(obj).clear();
|
||||
}
|
||||
};
|
||||
|
||||
static void reset(void* rows_ptr) noexcept
|
||||
{
|
||||
auto& rows = *static_cast<rows_t*>(rows_ptr);
|
||||
boost::mp11::mp_for_each<boost::mp11::mp_iota_c<sizeof...(StaticRow)>>(reset_fn{rows});
|
||||
}
|
||||
|
||||
template <std::size_t I>
|
||||
static error_code do_parse(span<const std::size_t> pos_map, span<const field_view> from, void* to)
|
||||
{
|
||||
auto& v = std::get<I>(*static_cast<rows_t*>(to));
|
||||
v.emplace_back();
|
||||
return parse(pos_map, from, v.back());
|
||||
}
|
||||
|
||||
template <std::size_t I>
|
||||
static constexpr results_resultset_descriptor create_descriptor()
|
||||
{
|
||||
using T = mp11::mp_at_c<mp11::mp_list<StaticRow...>, I>;
|
||||
return {
|
||||
get_row_size<T>(),
|
||||
get_row_name_table<T>(),
|
||||
&meta_check<T>,
|
||||
&do_parse<I>,
|
||||
};
|
||||
}
|
||||
|
||||
template <std::size_t... I>
|
||||
static constexpr std::array<results_resultset_descriptor, sizeof...(StaticRow)> create_descriptors(mp11::index_sequence<
|
||||
I...>)
|
||||
{
|
||||
return {{create_descriptor<I>()...}};
|
||||
}
|
||||
};
|
||||
|
||||
template <class... StaticRow>
|
||||
constexpr std::array<results_resultset_descriptor, sizeof...(StaticRow)>
|
||||
results_resultset_descriptor_table = results_fns<StaticRow...>::create_descriptors(
|
||||
mp11::make_index_sequence<sizeof...(StaticRow)>()
|
||||
);
|
||||
|
||||
template <BOOST_MYSQL_STATIC_ROW... StaticRow>
|
||||
class static_results_impl
|
||||
{
|
||||
// Data that requires knowing template params
|
||||
struct
|
||||
{
|
||||
results_rows_t<StaticRow...> rows;
|
||||
std::array<std::size_t, max_num_columns<StaticRow...>> pos_map{};
|
||||
std::array<static_per_resultset_data, sizeof...(StaticRow)> per_resultset{};
|
||||
} data_;
|
||||
|
||||
// The type-erased impl, that will use pointers to the above storage
|
||||
static_results_erased_impl impl_;
|
||||
|
||||
results_external_data::ptr_data ptr_data() noexcept
|
||||
{
|
||||
return {
|
||||
&data_.rows,
|
||||
data_.pos_map.data(),
|
||||
data_.per_resultset.data(),
|
||||
};
|
||||
}
|
||||
|
||||
void set_pointers() noexcept { impl_.ext_data().set_pointers(ptr_data()); }
|
||||
|
||||
public:
|
||||
static_results_impl() noexcept
|
||||
: impl_(results_external_data(
|
||||
results_resultset_descriptor_table<StaticRow...>,
|
||||
&results_fns<StaticRow...>::reset,
|
||||
ptr_data()
|
||||
))
|
||||
{
|
||||
}
|
||||
|
||||
static_results_impl(const static_results_impl& rhs) : data_(rhs.data_), impl_(rhs.impl_)
|
||||
{
|
||||
set_pointers();
|
||||
}
|
||||
|
||||
static_results_impl(static_results_impl&& rhs) noexcept
|
||||
: data_(std::move(rhs.data_)), impl_(std::move(rhs.impl_))
|
||||
{
|
||||
set_pointers();
|
||||
}
|
||||
|
||||
static_results_impl& operator=(const static_results_impl& rhs)
|
||||
{
|
||||
data_ = rhs.data_;
|
||||
impl_ = rhs.impl_;
|
||||
set_pointers();
|
||||
return *this;
|
||||
}
|
||||
|
||||
static_results_impl& operator=(static_results_impl&& rhs)
|
||||
{
|
||||
data_ = std::move(rhs.data_);
|
||||
impl_ = std::move(rhs.impl_);
|
||||
set_pointers();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// User facing
|
||||
template <std::size_t I>
|
||||
boost::span<const typename std::tuple_element<I, std::tuple<StaticRow...>>::type> get_rows(
|
||||
) const noexcept
|
||||
{
|
||||
return std::get<I>(data_.rows);
|
||||
}
|
||||
|
||||
const static_results_erased_impl& get_interface() const noexcept { return impl_; }
|
||||
static_results_erased_impl& get_interface() noexcept { return impl_; }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_MYSQL_CXX14
|
||||
|
||||
#endif
|
@ -10,9 +10,9 @@
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
@ -28,7 +28,7 @@ template <class Stream>
|
||||
void execute_impl(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
results& output,
|
||||
execution_processor& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
@ -38,7 +38,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute_impl(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
results& output,
|
||||
execution_processor& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
@ -1,51 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HELPERS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HELPERS_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline void process_available_rows(
|
||||
channel_base& channel,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Process all read messages until they run out, an error happens
|
||||
// or an EOF is received
|
||||
st.on_row_batch_start();
|
||||
while (channel.has_read_messages() && st.should_read_rows())
|
||||
{
|
||||
// Get the row message
|
||||
auto message = channel.next_read_message(st.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Deserialize the row
|
||||
deserialize_row(message, channel.current_capabilities(), channel.flavor(), st, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
st.on_row_batch_finish();
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -10,13 +10,10 @@
|
||||
|
||||
#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/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/execution_request.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
@ -28,7 +25,7 @@ template <class Stream, BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void execute(
|
||||
channel<Stream>& channel,
|
||||
const ExecutionRequest& req,
|
||||
results& output,
|
||||
execution_processor& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
@ -41,7 +38,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute(
|
||||
channel<Stream>& chan,
|
||||
ExecutionRequest&& req,
|
||||
results& output,
|
||||
execution_processor& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
@ -50,7 +47,7 @@ template <class Stream, BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void start_execution(
|
||||
channel<Stream>& channel,
|
||||
const ExecutionRequest& req,
|
||||
execution_state& st,
|
||||
execution_processor& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
@ -63,7 +60,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_execution(
|
||||
channel<Stream>& chan,
|
||||
ExecutionRequest&& req,
|
||||
execution_state& st,
|
||||
execution_processor& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/execute_impl.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/helpers.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_execution_messages.hpp>
|
||||
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
|
||||
@ -27,21 +27,21 @@ struct execute_impl_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
resultset_encoding enc_;
|
||||
execution_state_impl& st_;
|
||||
execution_processor& output_;
|
||||
diagnostics& diag_;
|
||||
|
||||
execute_impl_op(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
execution_state_impl& st,
|
||||
execution_processor& output,
|
||||
diagnostics& diag
|
||||
) noexcept
|
||||
: chan_(chan), enc_(enc), st_(st), diag_(diag)
|
||||
: chan_(chan), enc_(enc), output_(output), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
void operator()(Self& self, error_code err = {}, std::size_t = 0)
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
@ -55,31 +55,22 @@ struct execute_impl_op : boost::asio::coroutine
|
||||
{
|
||||
// Setup
|
||||
diag_.clear();
|
||||
st_.reset(enc_, nullptr); // rows owned by st
|
||||
output_.reset(enc_, chan_.meta_mode());
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
BOOST_ASIO_CORO_YIELD chan_
|
||||
.async_write(chan_.shared_buffer(), st_.sequence_number(), std::move(self));
|
||||
.async_write(chan_.shared_buffer(), output_.sequence_number(), std::move(self));
|
||||
|
||||
while (!st_.complete())
|
||||
while (!output_.is_complete())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD async_read_resultset_head(chan_, st_, diag_, std::move(self));
|
||||
|
||||
while (st_.should_read_rows())
|
||||
if (output_.is_reading_head())
|
||||
{
|
||||
// Ensure we have messages to be read
|
||||
if (!chan_.has_read_messages())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
}
|
||||
|
||||
// Process read messages
|
||||
process_available_rows(chan_, st_, err, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
BOOST_ASIO_CORO_YIELD async_read_resultset_head(chan_, output_, diag_, std::move(self));
|
||||
}
|
||||
else if (output_.is_reading_rows())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_read_some_rows_impl(chan_, output_, output_ref(), diag_, std::move(self));
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,42 +87,31 @@ template <class Stream>
|
||||
void boost::mysql::detail::execute_impl(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
results& result,
|
||||
execution_processor& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
diag.clear();
|
||||
auto& st = results_access::get_impl(result);
|
||||
st.reset(enc, nullptr);
|
||||
output.reset(enc, channel.meta_mode());
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
channel.write(channel.shared_buffer(), st.sequence_number(), err);
|
||||
channel.write(channel.shared_buffer(), output.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
while (!st.complete())
|
||||
while (!output.is_complete())
|
||||
{
|
||||
if (st.should_read_head())
|
||||
if (output.is_reading_head())
|
||||
{
|
||||
read_resultset_head(channel, st, err, diag);
|
||||
read_resultset_head(channel, output, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
while (st.should_read_rows())
|
||||
else if (output.is_reading_rows())
|
||||
{
|
||||
// Ensure we have messages to be read
|
||||
if (!channel.has_read_messages())
|
||||
{
|
||||
channel.read_some(err);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
// Process read messages
|
||||
process_available_rows(channel, st, err, diag);
|
||||
read_some_rows_impl(channel, output, output_ref(), err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
@ -143,13 +123,13 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_cod
|
||||
boost::mysql::detail::async_execute_impl(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
results& result,
|
||||
execution_processor& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
|
||||
execute_impl_op<Stream>(chan, enc, results_access::get_impl(result), diag),
|
||||
execute_impl_op<Stream>(chan, enc, output, diag),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
|
@ -10,15 +10,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/execution_request.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/execute_impl.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/high_level_execution.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/prepared_statement_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/query_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/typing/writable_field_traits.hpp>
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
|
||||
@ -75,13 +75,13 @@ void serialize_stmt_exec_req(
|
||||
serialize_message(request, chan.current_capabilities(), chan.shared_buffer());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... T, std::size_t... I>
|
||||
template <class... T, std::size_t... I>
|
||||
std::array<field_view, sizeof...(T)> tuple_to_array_impl(const std::tuple<T...>& t, boost::mp11::index_sequence<I...>) noexcept
|
||||
{
|
||||
return std::array<field_view, sizeof...(T)>{{field_view(std::get<I>(t))...}};
|
||||
return std::array<field_view, sizeof...(T)>{{to_field(std::get<I>(t))...}};
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... 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, boost::mp11::make_index_sequence<sizeof...(T)>());
|
||||
@ -93,26 +93,26 @@ inline error_code check_num_params(const statement& stmt, std::size_t param_coun
|
||||
}
|
||||
|
||||
// Statement, tuple
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
resultset_encoding get_encoding(const bound_statement_tuple<FieldLikeTuple>&)
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple>
|
||||
resultset_encoding get_encoding(const bound_statement_tuple<WritableFieldTuple>&)
|
||||
{
|
||||
return resultset_encoding::binary;
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void serialize_execution_request(const bound_statement_tuple<FieldLikeTuple>& req, channel_base& chan)
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple>
|
||||
void serialize_execution_request(const bound_statement_tuple<WritableFieldTuple>& req, channel_base& chan)
|
||||
{
|
||||
const auto& impl = statement_access::get_impl_tuple(req);
|
||||
auto arr = tuple_to_array(impl.params);
|
||||
serialize_stmt_exec_req(chan, impl.stmt, arr.begin(), arr.end());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
error_code check_client_errors(const bound_statement_tuple<FieldLikeTuple>& req)
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple>
|
||||
error_code check_client_errors(const bound_statement_tuple<WritableFieldTuple>& req)
|
||||
{
|
||||
return check_num_params(
|
||||
statement_access::get_impl_tuple(req).stmt,
|
||||
std::tuple_size<FieldLikeTuple>::value
|
||||
std::tuple_size<WritableFieldTuple>::value
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ struct initiate_execute
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
const ExecutionRequest& req,
|
||||
results& result,
|
||||
execution_processor& result,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
@ -182,7 +182,7 @@ struct initiate_start_execution
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
const ExecutionRequest& req,
|
||||
execution_state& st,
|
||||
execution_processor& st,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
@ -214,11 +214,12 @@ template <class Stream, BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void boost::mysql::detail::execute(
|
||||
channel<Stream>& channel,
|
||||
const ExecutionRequest& req,
|
||||
results& result,
|
||||
execution_processor& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
diag.clear();
|
||||
err = check_client_errors(req);
|
||||
if (err)
|
||||
return;
|
||||
@ -235,7 +236,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_cod
|
||||
boost::mysql::detail::async_execute(
|
||||
channel<Stream>& chan,
|
||||
ExecutionRequest&& req,
|
||||
results& result,
|
||||
execution_processor& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
@ -254,11 +255,12 @@ template <class Stream, BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void boost::mysql::detail::start_execution(
|
||||
channel<Stream>& channel,
|
||||
const ExecutionRequest& req,
|
||||
execution_state& st,
|
||||
execution_processor& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
diag.clear();
|
||||
err = check_client_errors(req);
|
||||
if (err)
|
||||
return;
|
||||
@ -275,7 +277,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_cod
|
||||
boost::mysql::detail::async_start_execution(
|
||||
channel<Stream>& chan,
|
||||
ExecutionRequest&& req,
|
||||
execution_state& st,
|
||||
execution_processor& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
|
@ -14,9 +14,9 @@
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_execute_response.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_execution_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/process_error_packet.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
@ -25,74 +25,57 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class read_resultset_head_processor
|
||||
inline error_code process_execution_response(
|
||||
channel_base& chan,
|
||||
execution_processor& proc,
|
||||
boost::asio::const_buffer msg,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
channel_base& chan_;
|
||||
execution_state_impl& st_;
|
||||
diagnostics& diag_;
|
||||
|
||||
public:
|
||||
read_resultset_head_processor(channel_base& chan, execution_state_impl& st, diagnostics& diag) noexcept
|
||||
: chan_(chan), st_(st), diag_(diag)
|
||||
auto response = deserialize_execute_response(msg, chan.current_capabilities(), chan.flavor(), diag);
|
||||
error_code err;
|
||||
switch (response.type)
|
||||
{
|
||||
case execute_response::type_t::error: err = response.data.err; break;
|
||||
case execute_response::type_t::ok_packet:
|
||||
err = proc.on_head_ok_packet(response.data.ok_pack, diag);
|
||||
break;
|
||||
case execute_response::type_t::num_fields: proc.on_num_meta(response.data.num_fields); break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void clear_diag() noexcept { diag_.clear(); }
|
||||
|
||||
error_code process_response(boost::asio::const_buffer msg)
|
||||
{
|
||||
error_code err;
|
||||
auto
|
||||
response = deserialize_execute_response(msg, chan_.current_capabilities(), chan_.flavor(), diag_);
|
||||
switch (response.type)
|
||||
{
|
||||
case execute_response::type_t::error: err = response.data.err; break;
|
||||
case execute_response::type_t::ok_packet: st_.on_head_ok_packet(response.data.ok_pack); break;
|
||||
case execute_response::type_t::num_fields: st_.on_num_meta(response.data.num_fields); break;
|
||||
}
|
||||
inline error_code process_field_definition(channel_base& chan, execution_processor& proc, diagnostics& diag)
|
||||
{
|
||||
// Read the field definition packet (it's cached at this point)
|
||||
assert(chan.has_read_messages());
|
||||
error_code err;
|
||||
auto msg = chan.next_read_message(proc.sequence_number(), err);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
error_code process_field_definition()
|
||||
{
|
||||
// Read the field definition packet (it's cached at this point)
|
||||
assert(chan_.has_read_messages());
|
||||
error_code err;
|
||||
auto msg = chan_.next_read_message(st_.sequence_number(), err);
|
||||
if (err)
|
||||
return err;
|
||||
// Deserialize
|
||||
column_definition_packet field_definition{};
|
||||
err = deserialize_message(msg, field_definition, chan.current_capabilities());
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// Deserialize
|
||||
column_definition_packet field_definition{};
|
||||
deserialization_context ctx(msg, chan_.current_capabilities());
|
||||
err = deserialize_message(ctx, field_definition);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// Notify the state object
|
||||
st_.on_meta(field_definition, chan_.meta_mode());
|
||||
return error_code();
|
||||
}
|
||||
|
||||
channel_base& get_channel() noexcept { return chan_; }
|
||||
execution_state_impl& state() noexcept { return st_; }
|
||||
};
|
||||
// Notify the processor
|
||||
return proc.on_meta(field_definition, diag);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
struct read_resultset_head_op : boost::asio::coroutine
|
||||
{
|
||||
read_resultset_head_processor processor_;
|
||||
channel<Stream>& chan_;
|
||||
execution_processor& proc_;
|
||||
diagnostics& diag_;
|
||||
|
||||
read_resultset_head_op(channel<Stream>& chan, execution_state_impl& st, diagnostics& diag)
|
||||
: processor_(chan, st, diag)
|
||||
read_resultset_head_op(channel<Stream>& chan, execution_processor& proc, diagnostics& diag)
|
||||
: chan_(chan), proc_(proc), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
channel<Stream>& get_channel() noexcept
|
||||
{
|
||||
return static_cast<channel<Stream>&>(processor_.get_channel());
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {}, boost::asio::const_buffer read_message = {})
|
||||
{
|
||||
@ -106,10 +89,11 @@ struct read_resultset_head_op : boost::asio::coroutine
|
||||
// Non-error path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
processor_.clear_diag();
|
||||
// Setup
|
||||
diag_.clear();
|
||||
|
||||
// If we're not reading head, return
|
||||
if (!processor_.state().should_read_head())
|
||||
if (!proc_.is_reading_head())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
self.complete(error_code());
|
||||
@ -117,14 +101,11 @@ struct read_resultset_head_op : boost::asio::coroutine
|
||||
}
|
||||
|
||||
// Read the response
|
||||
BOOST_ASIO_CORO_YIELD get_channel().async_read_one(
|
||||
processor_.state().sequence_number(),
|
||||
std::move(self)
|
||||
);
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_one(proc_.sequence_number(), std::move(self));
|
||||
|
||||
// Response may be: ok_packet, err_packet, local infile request
|
||||
// (not implemented), or response with fields
|
||||
err = processor_.process_response(read_message);
|
||||
err = process_execution_response(chan_, proc_, read_message, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
@ -132,16 +113,16 @@ struct read_resultset_head_op : boost::asio::coroutine
|
||||
}
|
||||
|
||||
// Read all of the field definitions
|
||||
while (processor_.state().should_read_meta())
|
||||
while (proc_.is_reading_meta())
|
||||
{
|
||||
// Read from the stream if we need it
|
||||
if (!get_channel().has_read_messages())
|
||||
if (!chan_.has_read_messages())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD get_channel().async_read_some(std::move(self));
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
}
|
||||
|
||||
// Process the metadata packet
|
||||
err = processor_.process_field_definition();
|
||||
err = process_field_definition(chan_, proc_, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
@ -150,7 +131,7 @@ struct read_resultset_head_op : boost::asio::coroutine
|
||||
}
|
||||
|
||||
// No EOF packet is expected here, as we require deprecate EOF capabilities
|
||||
self.complete(error_code());
|
||||
self.complete(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -162,32 +143,32 @@ struct read_resultset_head_op : boost::asio::coroutine
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::read_resultset_head(
|
||||
channel<Stream>& chan,
|
||||
execution_state_impl& st,
|
||||
execution_processor& proc,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
read_resultset_head_processor processor(chan, st, diag);
|
||||
processor.clear_diag();
|
||||
err = error_code();
|
||||
diag.clear();
|
||||
|
||||
// If we're not reading head, return
|
||||
if (!st.should_read_head())
|
||||
if (!proc.is_reading_head())
|
||||
return;
|
||||
|
||||
// Read the response
|
||||
auto msg = chan.read_one(st.sequence_number(), err);
|
||||
auto msg = chan.read_one(proc.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Response may be: ok_packet, err_packet, local infile request
|
||||
// (not implemented), or response with fields
|
||||
err = processor.process_response(msg);
|
||||
err = process_execution_response(chan, proc, msg, diag);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read all of the field definitions (zero if empty resultset)
|
||||
while (st.should_read_meta())
|
||||
while (proc.is_reading_meta())
|
||||
{
|
||||
// Read from the stream if required
|
||||
if (!chan.has_read_messages())
|
||||
@ -198,7 +179,7 @@ void boost::mysql::detail::read_resultset_head(
|
||||
}
|
||||
|
||||
// Process the packet
|
||||
err = processor.process_field_definition();
|
||||
err = process_field_definition(chan, proc, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
@ -208,13 +189,13 @@ template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::err
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::detail::async_read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
execution_processor& proc,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
read_resultset_head_op<Stream>(channel, st, diag),
|
||||
read_resultset_head_op<Stream>(channel, proc, diag),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
|
@ -1,134 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_HPP
|
||||
|
||||
#pragma once
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/row.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/helpers.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
struct read_some_rows_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
execution_state_impl& st_;
|
||||
|
||||
read_some_rows_op(channel<Stream>& chan, diagnostics& diag, execution_state_impl& st) noexcept
|
||||
: chan_(chan), diag_(diag), st_(st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, rows_view());
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
diag_.clear();
|
||||
|
||||
// If we are not reading rows, return
|
||||
if (!st_.should_read_rows())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
self.complete(error_code(), rows_view());
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Read at least one message
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
|
||||
// Process messages
|
||||
process_available_rows(chan_, st_, err, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, rows_view());
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
self.complete(error_code(), st_.get_external_rows());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
boost::mysql::rows_view boost::mysql::detail::read_some_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto& impl = execution_state_access::get_impl(st);
|
||||
|
||||
// If we are not reading rows, just return
|
||||
if (!impl.should_read_rows())
|
||||
{
|
||||
return rows_view();
|
||||
}
|
||||
|
||||
// Read from the stream until there is at least one message
|
||||
channel.read_some(err);
|
||||
if (err)
|
||||
return rows_view();
|
||||
|
||||
// Process read messages
|
||||
process_available_rows(channel, impl, err, diag);
|
||||
if (err)
|
||||
return rows_view();
|
||||
|
||||
return impl.get_external_rows();
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
|
||||
CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, boost::mysql::rows_view))
|
||||
boost::mysql::detail::async_read_some_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
|
||||
read_some_rows_op<Stream>(channel, diag, execution_state_access::get_impl(st)),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_IPP_ */
|
@ -0,0 +1,99 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_DYNAMIC_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_DYNAMIC_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_dynamic.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline rows_view get_some_rows(const channel_base& ch, const execution_state_impl& st)
|
||||
{
|
||||
return rows_view_access::construct(
|
||||
ch.shared_fields().data(),
|
||||
ch.shared_fields().size(),
|
||||
st.meta().size()
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
struct read_some_rows_dynamic_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
execution_state_impl& st_;
|
||||
|
||||
read_some_rows_dynamic_op(channel<Stream>& chan, diagnostics& diag, execution_state_impl& st) noexcept
|
||||
: chan_(chan), diag_(diag), st_(st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {}, std::size_t = 0)
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, rows_view());
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
chan_.shared_fields().clear();
|
||||
BOOST_ASIO_CORO_YIELD async_read_some_rows_impl(chan_, st_, output_ref(), diag_, std::move(self));
|
||||
self.complete(error_code(), get_some_rows(chan_, st_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
boost::mysql::rows_view boost::mysql::detail::read_some_rows_dynamic(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
channel.shared_fields().clear();
|
||||
read_some_rows_impl(channel, st, output_ref(), err, diag);
|
||||
if (err)
|
||||
return rows_view();
|
||||
return get_some_rows(channel, st);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
|
||||
CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, boost::mysql::rows_view))
|
||||
boost::mysql::detail::async_read_some_rows_dynamic(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
|
||||
read_some_rows_dynamic_op<Stream>(channel, diag, st),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_IPP_ */
|
@ -0,0 +1,178 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_SOME_ROWS_IMPL_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_execution_messages.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
BOOST_ATTRIBUTE_NODISCARD inline error_code process_some_rows(
|
||||
channel_base& chan,
|
||||
execution_processor& proc,
|
||||
output_ref output,
|
||||
std::size_t& read_rows,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Process all read messages until they run out, an error happens
|
||||
// or an EOF is received
|
||||
read_rows = 0;
|
||||
error_code err;
|
||||
proc.on_row_batch_start();
|
||||
while (chan.has_read_messages() && proc.is_reading_rows() && read_rows < output.max_size())
|
||||
{
|
||||
auto res = deserialize_row_message(chan, proc.sequence_number(), diag);
|
||||
if (res.type == row_message::type_t::error)
|
||||
{
|
||||
err = res.data.err;
|
||||
}
|
||||
else if (res.type == row_message::type_t::row)
|
||||
{
|
||||
output.set_offset(read_rows);
|
||||
err = proc.on_row(res.data.ctx, output, chan.shared_fields());
|
||||
if (!err)
|
||||
++read_rows;
|
||||
}
|
||||
else
|
||||
{
|
||||
err = proc.on_row_ok_packet(res.data.ok_pack);
|
||||
}
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
proc.on_row_batch_finish();
|
||||
return error_code();
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
struct read_some_rows_impl_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
execution_processor& proc_;
|
||||
output_ref output_;
|
||||
|
||||
read_some_rows_impl_op(
|
||||
channel<Stream>& chan,
|
||||
diagnostics& diag,
|
||||
execution_processor& proc,
|
||||
output_ref output
|
||||
) noexcept
|
||||
: chan_(chan), diag_(diag), proc_(proc), output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
std::size_t read_rows = 0;
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
diag_.clear();
|
||||
|
||||
// If we are not reading rows, return
|
||||
if (!proc_.is_reading_rows())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
self.complete(error_code(), 0);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Read at least one message
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
|
||||
// Process messages
|
||||
err = process_some_rows(chan_, proc_, output_, read_rows, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, 0);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
self.complete(error_code(), read_rows);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
std::size_t boost::mysql::detail::read_some_rows_impl(
|
||||
channel<Stream>& chan,
|
||||
execution_processor& proc,
|
||||
const output_ref& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
err.clear();
|
||||
diag.clear();
|
||||
|
||||
// If we are not reading rows, just return
|
||||
if (!proc.is_reading_rows())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read from the stream until there is at least one message
|
||||
chan.read_some(err);
|
||||
if (err)
|
||||
return 0;
|
||||
|
||||
// Process read messages
|
||||
std::size_t read_rows = 0;
|
||||
err = process_some_rows(chan, proc, output, read_rows, diag);
|
||||
if (err)
|
||||
return 0;
|
||||
|
||||
return read_rows;
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, std::size_t))
|
||||
boost::mysql::detail::async_read_some_rows_impl(
|
||||
channel<Stream>& chan,
|
||||
execution_processor& proc,
|
||||
const output_ref& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, std::size_t)>(
|
||||
read_some_rows_impl_op<Stream>(chan, diag, proc, output),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_IPP_ */
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
|
||||
@ -25,16 +24,16 @@ struct start_execution_impl_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
resultset_encoding enc_;
|
||||
execution_state_impl& st_;
|
||||
execution_processor& proc_;
|
||||
diagnostics& diag_;
|
||||
|
||||
start_execution_impl_op(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
execution_state_impl& st,
|
||||
execution_processor& proc,
|
||||
diagnostics& diag
|
||||
)
|
||||
: chan_(chan), enc_(enc), st_(st), diag_(diag)
|
||||
: chan_(chan), enc_(enc), proc_(proc), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
@ -53,15 +52,15 @@ struct start_execution_impl_op : boost::asio::coroutine
|
||||
{
|
||||
// Setup
|
||||
diag_.clear();
|
||||
st_.reset(enc_, &chan_.shared_fields());
|
||||
proc_.reset(enc_, chan_.meta_mode());
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
BOOST_ASIO_CORO_YIELD chan_
|
||||
.async_write(chan_.shared_buffer(), st_.sequence_number(), std::move(self));
|
||||
.async_write(chan_.shared_buffer(), proc_.sequence_number(), std::move(self));
|
||||
|
||||
// Read the first resultset's head
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_read_resultset_head(chan_, st_, diag_, std::move(self));
|
||||
async_read_resultset_head(chan_, proc_, diag_, std::move(self));
|
||||
|
||||
self.complete(error_code());
|
||||
}
|
||||
@ -76,24 +75,22 @@ template <class Stream>
|
||||
void boost::mysql::detail::start_execution_impl(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
execution_state& st,
|
||||
execution_processor& proc,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
auto& impl = execution_state_access::get_impl(st);
|
||||
diag.clear();
|
||||
|
||||
impl.reset(enc, &channel.shared_fields());
|
||||
proc.reset(enc, channel.meta_mode());
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
channel.write(channel.shared_buffer(), impl.sequence_number(), err);
|
||||
channel.write(channel.shared_buffer(), proc.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read the first resultset's head
|
||||
read_resultset_head(channel, impl, err, diag);
|
||||
read_resultset_head(channel, proc, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
@ -103,13 +100,13 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_cod
|
||||
boost::mysql::detail::async_start_execution_impl(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
execution_state& st,
|
||||
execution_processor& proc,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
start_execution_impl_op<Stream>(channel, enc, execution_state_access::get_impl(st), diag),
|
||||
start_execution_impl_op<Stream>(channel, enc, proc, diag),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
@ -22,7 +22,7 @@ namespace detail {
|
||||
template <class Stream>
|
||||
void read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
execution_processor& proc,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
@ -31,7 +31,7 @@ template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::err
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
execution_processor& proc,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
@ -5,32 +5,36 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_DYNAMIC_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_DYNAMIC_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
rows_view read_some_rows(channel<Stream>& channel, execution_state& st, error_code& err, diagnostics& diag);
|
||||
rows_view read_some_rows_dynamic(
|
||||
channel<Stream>& chan,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
|
||||
CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
|
||||
async_read_some_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& result,
|
||||
async_read_some_rows_dynamic(
|
||||
channel<Stream>& chan,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
@ -39,6 +43,6 @@ async_read_some_rows(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/read_some_rows.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/impl/read_some_rows_dynamic.hpp>
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_HPP_ */
|
||||
#endif
|
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
std::size_t read_some_rows_impl(
|
||||
channel<Stream>& chan,
|
||||
execution_processor& proc,
|
||||
const output_ref& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t))
|
||||
async_read_some_rows_impl(
|
||||
channel<Stream>& chan,
|
||||
execution_processor& proc,
|
||||
const output_ref& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/read_some_rows_impl.hpp>
|
||||
|
||||
#endif
|
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_STATIC_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_STATIC_HPP
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/static_execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream, class SpanRowType, class... RowType>
|
||||
std::size_t read_some_rows_static(
|
||||
channel<Stream>& chan,
|
||||
static_execution_state<RowType...>& st,
|
||||
span<SpanRowType> output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
constexpr std::size_t index = detail::get_type_index<SpanRowType, RowType...>();
|
||||
static_assert(
|
||||
index != detail::index_not_found,
|
||||
"SpanRowType must be one of the types returned by the query"
|
||||
);
|
||||
|
||||
return read_some_rows_impl(
|
||||
chan,
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
detail::output_ref(output, index),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class SpanRowType,
|
||||
class... RowType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
|
||||
async_read_some_rows_static(
|
||||
channel<Stream>& chan,
|
||||
static_execution_state<RowType...>& st,
|
||||
span<SpanRowType> output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
constexpr std::size_t index = detail::get_type_index<SpanRowType, RowType...>();
|
||||
static_assert(
|
||||
index != detail::index_not_found,
|
||||
"SpanRowType must be one of the types returned by the query"
|
||||
);
|
||||
|
||||
return async_read_some_rows_impl(
|
||||
chan,
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
detail::output_ref(output, index),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_MYSQL_CXX14
|
||||
|
||||
#endif
|
@ -10,9 +10,9 @@
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
@ -28,7 +28,7 @@ template <class Stream>
|
||||
void start_execution_impl(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding encoding,
|
||||
execution_state& st,
|
||||
execution_processor& proc,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
@ -38,7 +38,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_execution_impl(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding encoding,
|
||||
execution_state& st,
|
||||
execution_processor& proc,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
@ -1,62 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_EXECUTE_RESPONSE_HPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_EXECUTE_RESPONSE_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/db_flavor.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Exposed here for the sake of testing
|
||||
struct execute_response
|
||||
{
|
||||
enum class type_t
|
||||
{
|
||||
num_fields,
|
||||
ok_packet,
|
||||
error
|
||||
} type;
|
||||
union data_t
|
||||
{
|
||||
static_assert(std::is_trivially_destructible<error_code>::value, "");
|
||||
|
||||
std::size_t num_fields;
|
||||
ok_packet ok_pack;
|
||||
error_code err;
|
||||
|
||||
data_t(size_t v) noexcept : num_fields(v) {}
|
||||
data_t(const ok_packet& v) noexcept : ok_pack(v) {}
|
||||
data_t(error_code v) noexcept : err(v) {}
|
||||
} data;
|
||||
|
||||
execute_response(std::size_t v) noexcept : type(type_t::num_fields), data(v) {}
|
||||
execute_response(const ok_packet& v) noexcept : type(type_t::ok_packet), data(v) {}
|
||||
execute_response(error_code v) noexcept : type(type_t::error), data(v) {}
|
||||
};
|
||||
|
||||
inline execute_response deserialize_execute_response(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
) noexcept;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/protocol/impl/deserialize_execute_response.ipp>
|
||||
|
||||
#endif
|
@ -0,0 +1,104 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_EXECUTION_MESSAGES_HPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_EXECUTION_MESSAGES_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/db_flavor.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialization_context.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
struct execute_response
|
||||
{
|
||||
enum class type_t
|
||||
{
|
||||
num_fields,
|
||||
ok_packet,
|
||||
error
|
||||
} type;
|
||||
union data_t
|
||||
{
|
||||
static_assert(std::is_trivially_destructible<error_code>::value, "");
|
||||
|
||||
std::size_t num_fields;
|
||||
ok_packet ok_pack;
|
||||
error_code err;
|
||||
|
||||
data_t(size_t v) noexcept : num_fields(v) {}
|
||||
data_t(const ok_packet& v) noexcept : ok_pack(v) {}
|
||||
data_t(error_code v) noexcept : err(v) {}
|
||||
} data;
|
||||
|
||||
execute_response(std::size_t v) noexcept : type(type_t::num_fields), data(v) {}
|
||||
execute_response(const ok_packet& v) noexcept : type(type_t::ok_packet), data(v) {}
|
||||
execute_response(error_code v) noexcept : type(type_t::error), data(v) {}
|
||||
};
|
||||
|
||||
inline execute_response deserialize_execute_response(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
) noexcept;
|
||||
|
||||
struct row_message
|
||||
{
|
||||
enum class type_t
|
||||
{
|
||||
row,
|
||||
ok_packet,
|
||||
error
|
||||
} type;
|
||||
union data_t
|
||||
{
|
||||
static_assert(std::is_trivially_destructible<deserialization_context>::value, "");
|
||||
static_assert(std::is_trivially_destructible<ok_packet>::value, "");
|
||||
static_assert(std::is_trivially_destructible<error_code>::value, "");
|
||||
|
||||
deserialization_context ctx; // if row
|
||||
ok_packet ok_pack;
|
||||
error_code err;
|
||||
|
||||
data_t(const deserialization_context& ctx) noexcept : ctx(ctx) {}
|
||||
data_t(const ok_packet& ok_pack) noexcept : ok_pack(ok_pack) {}
|
||||
data_t(error_code err) noexcept : err(err) {}
|
||||
} data;
|
||||
|
||||
row_message(const deserialization_context& ctx) noexcept : type(type_t::row), data(ctx) {}
|
||||
row_message(const ok_packet& ok_pack) noexcept : type(type_t::ok_packet), data(ok_pack) {}
|
||||
row_message(error_code v) noexcept : type(type_t::error), data(v) {}
|
||||
};
|
||||
|
||||
inline row_message deserialize_row_message(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
inline row_message deserialize_row_message(
|
||||
channel_base& chan,
|
||||
std::uint8_t& sequence_number,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/protocol/impl/deserialize_execution_messages.ipp>
|
||||
|
||||
#endif
|
@ -8,42 +8,21 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_ROW_HPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_ROW_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/db_flavor.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/protocol/serialization_context.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline void deserialize_row(
|
||||
inline error_code deserialize_row(
|
||||
resultset_encoding encoding,
|
||||
deserialization_context& ctx,
|
||||
metadata_collection_view meta,
|
||||
field_view* output, // Should point to meta.size() field_view objects
|
||||
error_code& err
|
||||
);
|
||||
|
||||
inline void deserialize_row(
|
||||
boost::asio::const_buffer read_message,
|
||||
capabilities current_capabilities,
|
||||
db_flavor flavor,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
boost::span<field_view> output // Should point to meta.size() field_view objects
|
||||
);
|
||||
|
||||
} // namespace detail
|
||||
|
@ -1,438 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_EXECUTION_STATE_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_EXECUTION_STATE_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/string_view_offset.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/impl/rows_view.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// A rows-like collection that can hold the rows for multiple resultsets.
|
||||
// This class also helps gathering all the rows without incurring in dangling references.
|
||||
// Rows are read in batches:
|
||||
// - Open a batch calling start_batch()
|
||||
// - Call deserialize_row(), which will call add_row(). This adds raw storage for fields.
|
||||
// Fields are then deserialized there. string/blob field_view's point into the
|
||||
// connection's internal buffer.
|
||||
// - When the batch finishes, call finish_batch(). This will copy strings into thr row_impl
|
||||
// and transform these values into offsets, so the buffer can be grown.
|
||||
// - When the final OK packet is received, offsets are converted back into views,
|
||||
// by calling finish()
|
||||
class multi_rows
|
||||
{
|
||||
static constexpr std::size_t no_batch = std::size_t(-1);
|
||||
|
||||
row_impl impl_;
|
||||
std::size_t num_fields_at_batch_start_{no_batch};
|
||||
|
||||
bool has_active_batch() const noexcept { return num_fields_at_batch_start_ != no_batch; }
|
||||
|
||||
public:
|
||||
multi_rows() = default;
|
||||
|
||||
std::size_t num_fields() const noexcept { return impl_.fields().size(); }
|
||||
|
||||
rows_view rows_slice(std::size_t offset, std::size_t num_columns, std::size_t num_rows) const noexcept
|
||||
{
|
||||
return rows_view_access::construct(
|
||||
impl_.fields().data() + offset,
|
||||
num_rows * num_columns,
|
||||
num_columns
|
||||
);
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
impl_.clear();
|
||||
num_fields_at_batch_start_ = no_batch;
|
||||
}
|
||||
|
||||
void start_batch() noexcept
|
||||
{
|
||||
assert(!has_active_batch());
|
||||
num_fields_at_batch_start_ = num_fields();
|
||||
}
|
||||
|
||||
field_view* add_row(std::size_t num_fields)
|
||||
{
|
||||
assert(has_active_batch());
|
||||
return impl_.add_fields(num_fields);
|
||||
}
|
||||
|
||||
void finish_batch()
|
||||
{
|
||||
if (has_active_batch())
|
||||
{
|
||||
impl_.copy_strings_as_offsets(
|
||||
num_fields_at_batch_start_,
|
||||
impl_.fields().size() - num_fields_at_batch_start_
|
||||
);
|
||||
num_fields_at_batch_start_ = no_batch;
|
||||
}
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
finish_batch();
|
||||
impl_.offsets_to_string_views();
|
||||
}
|
||||
};
|
||||
|
||||
struct per_resultset_data
|
||||
{
|
||||
std::size_t num_columns{}; // Number of columns this resultset has
|
||||
std::size_t meta_offset{}; // Offset into the vector of metadata
|
||||
std::size_t field_offset; // Offset into the vector of fields (append mode only)
|
||||
std::size_t num_rows{}; // Number of rows this resultset has (append mode only)
|
||||
std::uint64_t affected_rows{}; // OK packet data
|
||||
std::uint64_t last_insert_id{}; // OK packet data
|
||||
std::uint16_t warnings{}; // OK packet data
|
||||
std::size_t info_offset{}; // Offset into the vector of info characters
|
||||
std::size_t info_size{}; // Number of characters that this resultset's info string has
|
||||
bool has_ok_packet_data{false}; // The OK packet information is default constructed, or actual data?
|
||||
bool is_out_params{false}; // Does this resultset contain OUT param information?
|
||||
};
|
||||
|
||||
// A container similar to a vector with SBO. To avoid depending on Boost.Container
|
||||
class resultset_container
|
||||
{
|
||||
bool first_has_data_{false};
|
||||
per_resultset_data first_;
|
||||
std::vector<per_resultset_data> rest_;
|
||||
|
||||
public:
|
||||
resultset_container() = default;
|
||||
std::size_t size() const noexcept { return !first_has_data_ ? 0 : rest_.size() + 1; }
|
||||
bool empty() const noexcept { return !first_has_data_; }
|
||||
void clear() noexcept
|
||||
{
|
||||
first_has_data_ = false;
|
||||
rest_.clear();
|
||||
}
|
||||
per_resultset_data& operator[](std::size_t i) noexcept
|
||||
{
|
||||
return const_cast<per_resultset_data&>(const_cast<const resultset_container&>(*this)[i]);
|
||||
}
|
||||
const per_resultset_data& operator[](std::size_t i) const noexcept
|
||||
{
|
||||
assert(i < size());
|
||||
return i == 0 ? first_ : rest_[i - 1];
|
||||
}
|
||||
per_resultset_data& back() noexcept
|
||||
{
|
||||
return const_cast<per_resultset_data&>(const_cast<const resultset_container&>(*this).back());
|
||||
}
|
||||
const per_resultset_data& back() const noexcept
|
||||
{
|
||||
assert(first_has_data_);
|
||||
return rest_.empty() ? first_ : rest_.back();
|
||||
}
|
||||
per_resultset_data& emplace_back()
|
||||
{
|
||||
if (!first_has_data_)
|
||||
{
|
||||
first_ = per_resultset_data();
|
||||
first_has_data_ = true;
|
||||
return first_;
|
||||
}
|
||||
else
|
||||
{
|
||||
rest_.emplace_back();
|
||||
return rest_.back();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class execution_state_impl
|
||||
{
|
||||
public:
|
||||
// Append mode=true is used by single-function operations. Metadata and info are
|
||||
// appended to the collections here. Append mode=false is used by multi-function
|
||||
// operations. Every new resultset wipes the previous one
|
||||
// In append mode, rows are owned by this class. In non-append mode,
|
||||
// they're owned by the external storage (usually the channel).
|
||||
execution_state_impl(bool append_mode) noexcept : append_mode_(append_mode) {}
|
||||
|
||||
bool is_append_mode() const noexcept { return append_mode_; }
|
||||
|
||||
// State accessors
|
||||
bool should_read_head() const noexcept { return state_ == state_t::reading_first_packet; }
|
||||
bool should_read_meta() const noexcept { return state_ == state_t::reading_metadata; }
|
||||
bool should_read_rows() const noexcept { return state_ == state_t::reading_rows; }
|
||||
bool complete() const noexcept { return state_ == state_t::complete; }
|
||||
|
||||
// State transitions
|
||||
void reset(detail::resultset_encoding encoding, std::vector<field_view>* field_storage) noexcept
|
||||
{
|
||||
state_ = state_t::reading_first_packet;
|
||||
seqnum_ = 0;
|
||||
encoding_ = encoding;
|
||||
remaining_meta_ = 0;
|
||||
meta_.clear();
|
||||
per_result_.clear();
|
||||
info_.clear();
|
||||
if (append_mode_)
|
||||
{
|
||||
rows_.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(field_storage != nullptr);
|
||||
external_rows_ = field_storage;
|
||||
}
|
||||
}
|
||||
|
||||
void on_num_meta(std::size_t num_columns)
|
||||
{
|
||||
assert(state_ == state_t::reading_first_packet);
|
||||
on_new_resultset();
|
||||
meta_.reserve(meta_.size() + num_columns);
|
||||
auto& resultset_data = current_resultset();
|
||||
resultset_data.num_columns = num_columns;
|
||||
remaining_meta_ = num_columns;
|
||||
state_ = state_t::reading_metadata;
|
||||
}
|
||||
|
||||
void on_meta(const detail::column_definition_packet& pack, metadata_mode mode)
|
||||
{
|
||||
assert(state_ == state_t::reading_metadata);
|
||||
meta_.push_back(metadata_access::construct(pack, mode == metadata_mode::full));
|
||||
if (--remaining_meta_ == 0)
|
||||
{
|
||||
state_ = state_t::reading_rows;
|
||||
}
|
||||
}
|
||||
|
||||
void on_row_batch_start() noexcept
|
||||
{
|
||||
assert(state_ == state_t::reading_rows);
|
||||
if (append_mode_)
|
||||
{
|
||||
rows_.start_batch();
|
||||
}
|
||||
else
|
||||
{
|
||||
external_rows_->clear();
|
||||
}
|
||||
}
|
||||
|
||||
field_view* add_row()
|
||||
{
|
||||
assert(state_ == state_t::reading_rows);
|
||||
std::size_t num_fields = current_resultset().num_columns;
|
||||
field_view* res = append_mode_ ? rows_.add_row(num_fields) : add_fields(*external_rows_, num_fields);
|
||||
++current_resultset().num_rows;
|
||||
return res;
|
||||
}
|
||||
|
||||
void on_row_batch_finish()
|
||||
{
|
||||
// it's legal to invoke this function from any state
|
||||
if (append_mode_)
|
||||
{
|
||||
rows_.finish_batch();
|
||||
}
|
||||
}
|
||||
|
||||
// When we receive an OK packet in an execute function, as the first packet
|
||||
void on_head_ok_packet(const ok_packet& pack)
|
||||
{
|
||||
assert(state_ == state_t::reading_first_packet);
|
||||
on_new_resultset();
|
||||
on_ok_packet_impl(pack);
|
||||
}
|
||||
|
||||
// When we receive an OK packet while reading rows
|
||||
void on_row_ok_packet(const ok_packet& pack)
|
||||
{
|
||||
assert(state_ == state_t::reading_rows);
|
||||
on_ok_packet_impl(pack);
|
||||
}
|
||||
|
||||
// Accessors for other protocol stuff
|
||||
resultset_encoding encoding() const noexcept { return encoding_; }
|
||||
std::uint8_t& sequence_number() noexcept { return seqnum_; }
|
||||
|
||||
metadata_collection_view current_resultset_meta() const noexcept
|
||||
{
|
||||
assert(state_ == state_t::reading_rows);
|
||||
return get_meta(per_result_.size() - 1);
|
||||
}
|
||||
|
||||
// Accessors for user-facing components
|
||||
std::size_t num_resultsets() const noexcept { return per_result_.size(); }
|
||||
|
||||
rows_view get_rows(std::size_t index) const noexcept
|
||||
{
|
||||
assert(append_mode_);
|
||||
const auto& resultset_data = per_result_[index];
|
||||
return rows_
|
||||
.rows_slice(resultset_data.field_offset, resultset_data.num_columns, resultset_data.num_rows);
|
||||
}
|
||||
|
||||
rows_view get_external_rows() const noexcept
|
||||
{
|
||||
assert(!append_mode_);
|
||||
return rows_view_access::construct(
|
||||
external_rows_->data(),
|
||||
external_rows_->size(),
|
||||
current_resultset().num_columns
|
||||
);
|
||||
}
|
||||
|
||||
metadata_collection_view get_meta(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = per_result_[index];
|
||||
return metadata_collection_view(
|
||||
meta_.data() + resultset_data.meta_offset,
|
||||
resultset_data.num_columns
|
||||
);
|
||||
}
|
||||
|
||||
std::uint64_t get_affected_rows(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset_with_ok_packet(index).affected_rows;
|
||||
}
|
||||
|
||||
std::uint64_t get_last_insert_id(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset_with_ok_packet(index).last_insert_id;
|
||||
}
|
||||
|
||||
unsigned get_warning_count(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset_with_ok_packet(index).warnings;
|
||||
}
|
||||
|
||||
string_view get_info(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& resultset_data = get_resultset_with_ok_packet(index);
|
||||
return string_view(info_.data() + resultset_data.info_offset, resultset_data.info_size);
|
||||
}
|
||||
|
||||
bool get_is_out_params(std::size_t index) const noexcept
|
||||
{
|
||||
return get_resultset_with_ok_packet(index).is_out_params;
|
||||
}
|
||||
|
||||
row_view get_out_params() const noexcept
|
||||
{
|
||||
assert(append_mode_ && state_ == state_t::complete);
|
||||
for (std::size_t i = 0; i < per_result_.size(); ++i)
|
||||
{
|
||||
if (per_result_[i].is_out_params)
|
||||
{
|
||||
auto res = get_rows(i);
|
||||
return res.empty() ? row_view() : res[0];
|
||||
}
|
||||
}
|
||||
return row_view();
|
||||
}
|
||||
|
||||
private:
|
||||
enum class state_t
|
||||
{
|
||||
reading_first_packet, // we're waiting for a resultset's 1st packet
|
||||
reading_metadata,
|
||||
reading_rows,
|
||||
complete
|
||||
};
|
||||
|
||||
void on_new_resultset() noexcept
|
||||
{
|
||||
// Clean stuff from previous resultsets if we're not in append mode
|
||||
if (!append_mode_)
|
||||
{
|
||||
meta_.clear();
|
||||
per_result_.clear();
|
||||
info_.clear();
|
||||
}
|
||||
|
||||
// Allocate a new per-resultset object
|
||||
auto& resultset_data = per_result_.emplace_back();
|
||||
resultset_data.meta_offset = meta_.size();
|
||||
resultset_data.field_offset = rows_.num_fields();
|
||||
resultset_data.info_offset = info_.size();
|
||||
}
|
||||
|
||||
void on_ok_packet_impl(const ok_packet& pack) noexcept
|
||||
{
|
||||
auto& resultset_data = current_resultset();
|
||||
resultset_data.affected_rows = pack.affected_rows.value;
|
||||
resultset_data.last_insert_id = pack.last_insert_id.value;
|
||||
resultset_data.warnings = pack.warnings;
|
||||
resultset_data.info_size = pack.info.value.size();
|
||||
resultset_data.has_ok_packet_data = true;
|
||||
resultset_data.is_out_params = pack.status_flags & SERVER_PS_OUT_PARAMS;
|
||||
info_.insert(info_.end(), pack.info.value.begin(), pack.info.value.end());
|
||||
if (pack.status_flags & SERVER_MORE_RESULTS_EXISTS)
|
||||
{
|
||||
state_ = state_t::reading_first_packet;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (append_mode_)
|
||||
{
|
||||
rows_.finish();
|
||||
}
|
||||
state_ = state_t::complete;
|
||||
}
|
||||
}
|
||||
|
||||
per_resultset_data& current_resultset() noexcept
|
||||
{
|
||||
assert(!per_result_.empty());
|
||||
return per_result_.back();
|
||||
}
|
||||
|
||||
const per_resultset_data& current_resultset() const noexcept
|
||||
{
|
||||
assert(!per_result_.empty());
|
||||
return per_result_.back();
|
||||
}
|
||||
|
||||
const per_resultset_data& get_resultset_with_ok_packet(std::size_t index) const noexcept
|
||||
{
|
||||
const auto& res = per_result_[index];
|
||||
assert(res.has_ok_packet_data);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool append_mode_{false};
|
||||
state_t state_{state_t::reading_first_packet};
|
||||
std::uint8_t seqnum_{};
|
||||
detail::resultset_encoding encoding_{detail::resultset_encoding::text};
|
||||
std::size_t remaining_meta_{};
|
||||
std::vector<metadata> meta_;
|
||||
resultset_container per_result_;
|
||||
std::vector<char> info_;
|
||||
multi_rows rows_; // if append_mode
|
||||
std::vector<field_view>* external_rows_{}; // if !append_mode
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -5,13 +5,13 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_EXECUTE_RESPONSE_IPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_EXECUTE_RESPONSE_IPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_EXECUTION_MESSAGES_IPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_EXECUTION_MESSAGES_IPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/protocol/deserialize_execute_response.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_errc.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_execution_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/process_error_packet.hpp>
|
||||
|
||||
namespace boost {
|
||||
@ -73,4 +73,58 @@ inline boost::mysql::detail::execute_response boost::mysql::detail::deserialize_
|
||||
}
|
||||
}
|
||||
|
||||
inline boost::mysql::detail::row_message boost::mysql::detail::deserialize_row_message(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Message type: row, error or eof?
|
||||
std::uint8_t msg_type = 0;
|
||||
deserialization_context ctx(msg, caps);
|
||||
auto deser_errc = deserialize(ctx, msg_type);
|
||||
if (deser_errc != deserialize_errc::ok)
|
||||
{
|
||||
return to_error_code(deser_errc);
|
||||
}
|
||||
|
||||
if (msg_type == eof_packet_header)
|
||||
{
|
||||
// end of resultset => this is a ok_packet, not a row
|
||||
ok_packet ok_pack;
|
||||
auto err = deserialize_message(ctx, ok_pack);
|
||||
if (err)
|
||||
return err;
|
||||
return ok_pack;
|
||||
}
|
||||
else if (msg_type == error_packet_header)
|
||||
{
|
||||
// An error occurred during the generation of the rows
|
||||
return process_error_packet(ctx, flavor, diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An actual row
|
||||
ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
inline boost::mysql::detail::row_message boost::mysql::detail::deserialize_row_message(
|
||||
channel_base& chan,
|
||||
std::uint8_t& sequence_number,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Get the row message
|
||||
error_code err;
|
||||
auto buff = chan.next_read_message(sequence_number, err);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// Deserialize it
|
||||
return deserialize_row_message(buff, chan.current_capabilities(), chan.flavor(), diag);
|
||||
}
|
||||
|
||||
#endif
|
@ -8,11 +8,10 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_ROW_IPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_ROW_IPP
|
||||
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/deserialize_binary_field.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_text_field.hpp>
|
||||
@ -108,60 +107,16 @@ inline error_code deserialize_binary_row(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
void boost::mysql::detail::deserialize_row(
|
||||
boost::mysql::error_code boost::mysql::detail::deserialize_row(
|
||||
resultset_encoding encoding,
|
||||
deserialization_context& ctx,
|
||||
metadata_collection_view meta,
|
||||
field_view* output,
|
||||
error_code& err
|
||||
boost::span<field_view> output
|
||||
)
|
||||
{
|
||||
err = encoding == detail::resultset_encoding::text ? deserialize_text_row(ctx, meta, output)
|
||||
: deserialize_binary_row(ctx, meta, output);
|
||||
}
|
||||
|
||||
void boost::mysql::detail::deserialize_row(
|
||||
boost::asio::const_buffer read_message,
|
||||
capabilities current_capabilities,
|
||||
db_flavor flavor,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
assert(st.should_read_rows());
|
||||
|
||||
// Message type: row, error or eof?
|
||||
std::uint8_t msg_type = 0;
|
||||
deserialization_context ctx(read_message, current_capabilities);
|
||||
auto deser_errc = deserialize(ctx, msg_type);
|
||||
if (deser_errc != deserialize_errc::ok)
|
||||
{
|
||||
err = to_error_code(deser_errc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg_type == eof_packet_header)
|
||||
{
|
||||
// end of resultset => this is a ok_packet, not a row
|
||||
ok_packet ok_pack;
|
||||
err = deserialize_message(ctx, ok_pack);
|
||||
if (err)
|
||||
return;
|
||||
st.on_row_ok_packet(ok_pack);
|
||||
}
|
||||
else if (msg_type == error_packet_header)
|
||||
{
|
||||
// An error occurred during the generation of the rows
|
||||
err = process_error_packet(ctx, flavor, diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An actual row
|
||||
ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message
|
||||
field_view* storage = st.add_row();
|
||||
deserialize_row(st.encoding(), ctx, st.current_resultset_meta(), storage, err);
|
||||
}
|
||||
assert(meta.size() == output.size());
|
||||
return encoding == detail::resultset_encoding::text ? deserialize_text_row(ctx, meta, output.data())
|
||||
: deserialize_binary_row(ctx, meta, output.data());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -27,7 +27,7 @@ inline boost::mysql::error_code boost::mysql::detail::process_error_packet(
|
||||
|
||||
// Error message
|
||||
string_view sv = error_packet.error_message.value;
|
||||
diagnostics_access::assign(diag, sv);
|
||||
diagnostics_access::assign_server(diag, sv);
|
||||
|
||||
// Error code
|
||||
if (get_common_error_message(error_packet.error_code))
|
||||
|
@ -27,10 +27,9 @@ inline string_view get_string(const std::uint8_t* from, std::size_t size)
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::int_lenenc,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, int_lenenc& output) noexcept
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::int_lenenc, boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, int_lenenc& output) noexcept
|
||||
{
|
||||
std::uint8_t first_byte = 0;
|
||||
auto err = deserialize(ctx, first_byte);
|
||||
@ -65,10 +64,9 @@ inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serializatio
|
||||
return err;
|
||||
}
|
||||
|
||||
inline void boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::int_lenenc,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
serialize_(serialization_context& ctx, int_lenenc input) noexcept
|
||||
inline void boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::int_lenenc, boost::mysql::detail::serialization_tag::none>::
|
||||
serialize_(serialization_context& ctx, int_lenenc input) noexcept
|
||||
{
|
||||
if (input.value < 251)
|
||||
{
|
||||
@ -91,10 +89,9 @@ inline void boost::mysql::detail::serialization_traits<
|
||||
}
|
||||
}
|
||||
|
||||
inline std::size_t boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::int_lenenc,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
get_size_(const serialization_context&, int_lenenc input) noexcept
|
||||
inline std::size_t boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::int_lenenc, boost::mysql::detail::serialization_tag::none>::
|
||||
get_size_(const serialization_context&, int_lenenc input) noexcept
|
||||
{
|
||||
if (input.value < 251)
|
||||
return 1;
|
||||
@ -106,10 +103,9 @@ inline std::size_t boost::mysql::detail::serialization_traits<
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::string_null,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_null& output) noexcept
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::string_null, boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_null& output) noexcept
|
||||
{
|
||||
auto string_end = std::find(ctx.first(), ctx.last(), 0);
|
||||
if (string_end == ctx.last())
|
||||
@ -121,20 +117,18 @@ inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serializatio
|
||||
return deserialize_errc::ok;
|
||||
}
|
||||
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::string_eof,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_eof& output) noexcept
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::string_eof, boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_eof& output) noexcept
|
||||
{
|
||||
output.value = get_string(ctx.first(), ctx.last() - ctx.first());
|
||||
ctx.set_first(ctx.last());
|
||||
return deserialize_errc::ok;
|
||||
}
|
||||
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serialization_traits<
|
||||
boost::mysql::detail::string_lenenc,
|
||||
boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_lenenc& output) noexcept
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::
|
||||
serialization_traits<boost::mysql::detail::string_lenenc, boost::mysql::detail::serialization_tag::none>::
|
||||
deserialize_(deserialization_context& ctx, string_lenenc& output) noexcept
|
||||
{
|
||||
int_lenenc length;
|
||||
auto err = deserialize(ctx, length);
|
||||
@ -142,7 +136,7 @@ inline boost::mysql::detail::deserialize_errc boost::mysql::detail::serializatio
|
||||
{
|
||||
return err;
|
||||
}
|
||||
if (length.value > std::numeric_limits<std::size_t>::max())
|
||||
if (length.value > (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
return deserialize_errc::protocol_value_error;
|
||||
}
|
||||
|
@ -9,11 +9,13 @@
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_SERIALIZATION_HPP
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/bytestring.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialization_context.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_errc.hpp>
|
||||
#include <boost/mysql/detail/protocol/protocol_types.hpp>
|
||||
#include <boost/mysql/detail/protocol/serialization_context.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
@ -52,10 +54,7 @@ struct serialization_traits<T, serialization_tag::plain_int>
|
||||
{
|
||||
static deserialize_errc deserialize_(deserialization_context& ctx, T& output) noexcept;
|
||||
static void serialize_(serialization_context& ctx, T input) noexcept;
|
||||
static constexpr std::size_t get_size_(const serialization_context&, T) noexcept
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
static constexpr std::size_t get_size_(const serialization_context&, T) noexcept { return sizeof(T); }
|
||||
};
|
||||
|
||||
// int3
|
||||
@ -82,10 +81,7 @@ struct serialization_traits<int3, serialization_tag::none>
|
||||
template <>
|
||||
struct serialization_traits<int_lenenc, serialization_tag::none>
|
||||
{
|
||||
static inline deserialize_errc deserialize_(
|
||||
deserialization_context& ctx,
|
||||
int_lenenc& output
|
||||
) noexcept;
|
||||
static inline deserialize_errc deserialize_(deserialization_context& ctx, int_lenenc& output) noexcept;
|
||||
static inline void serialize_(serialization_context& ctx, int_lenenc input) noexcept;
|
||||
static inline std::size_t get_size_(const serialization_context&, int_lenenc input) noexcept;
|
||||
};
|
||||
@ -94,10 +90,7 @@ struct serialization_traits<int_lenenc, serialization_tag::none>
|
||||
template <std::size_t N>
|
||||
struct serialization_traits<string_fixed<N>, serialization_tag::none>
|
||||
{
|
||||
static deserialize_errc deserialize_(
|
||||
deserialization_context& ctx,
|
||||
string_fixed<N>& output
|
||||
) noexcept
|
||||
static deserialize_errc deserialize_(deserialization_context& ctx, string_fixed<N>& output) noexcept
|
||||
{
|
||||
if (!ctx.enough_size(N))
|
||||
return deserialize_errc::incomplete_message;
|
||||
@ -119,10 +112,7 @@ struct serialization_traits<string_fixed<N>, serialization_tag::none>
|
||||
template <>
|
||||
struct serialization_traits<string_null, serialization_tag::none>
|
||||
{
|
||||
static inline deserialize_errc deserialize_(
|
||||
deserialization_context& ctx,
|
||||
string_null& output
|
||||
) noexcept;
|
||||
static inline deserialize_errc deserialize_(deserialization_context& ctx, string_null& output) noexcept;
|
||||
static inline void serialize_(serialization_context& ctx, string_null input) noexcept
|
||||
{
|
||||
ctx.write(input.value.data(), input.value.size());
|
||||
@ -138,10 +128,7 @@ struct serialization_traits<string_null, serialization_tag::none>
|
||||
template <>
|
||||
struct serialization_traits<string_eof, serialization_tag::none>
|
||||
{
|
||||
static inline deserialize_errc deserialize_(
|
||||
deserialization_context& ctx,
|
||||
string_eof& output
|
||||
) noexcept;
|
||||
static inline deserialize_errc deserialize_(deserialization_context& ctx, string_eof& output) noexcept;
|
||||
static inline void serialize_(serialization_context& ctx, string_eof input) noexcept
|
||||
{
|
||||
ctx.write(input.value.data(), input.value.size());
|
||||
@ -156,19 +143,13 @@ struct serialization_traits<string_eof, serialization_tag::none>
|
||||
template <>
|
||||
struct serialization_traits<string_lenenc, serialization_tag::none>
|
||||
{
|
||||
static inline deserialize_errc deserialize_(
|
||||
deserialization_context& ctx,
|
||||
string_lenenc& output
|
||||
) noexcept;
|
||||
static inline deserialize_errc deserialize_(deserialization_context& ctx, string_lenenc& output) noexcept;
|
||||
static inline void serialize_(serialization_context& ctx, string_lenenc input) noexcept
|
||||
{
|
||||
serialize(ctx, int_lenenc(input.value.size()));
|
||||
ctx.write(input.value.data(), input.value.size());
|
||||
}
|
||||
static inline std::size_t get_size_(
|
||||
const serialization_context& ctx,
|
||||
string_lenenc input
|
||||
) noexcept
|
||||
static inline std::size_t get_size_(const serialization_context& ctx, string_lenenc input) noexcept
|
||||
{
|
||||
return get_size(ctx, int_lenenc(input.value.size())) + input.value.size();
|
||||
}
|
||||
@ -191,10 +172,7 @@ struct serialization_traits<T, serialization_tag::enumeration>
|
||||
{
|
||||
serialize(ctx, static_cast<underlying_type>(input));
|
||||
}
|
||||
static std::size_t get_size_(const serialization_context&, T) noexcept
|
||||
{
|
||||
return sizeof(underlying_type);
|
||||
}
|
||||
static std::size_t get_size_(const serialization_context&, T) noexcept { return sizeof(underlying_type); }
|
||||
};
|
||||
|
||||
// Structs and commands
|
||||
@ -223,10 +201,7 @@ struct serialization_traits<T, serialization_tag::struct_with_fields>
|
||||
template <class T>
|
||||
struct noop_serialize
|
||||
{
|
||||
static inline std::size_t get_size_(const serialization_context&, const T&) noexcept
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline std::size_t get_size_(const serialization_context&, const T&) noexcept { return 0; }
|
||||
static inline void serialize_(serialization_context&, const T&) noexcept {}
|
||||
};
|
||||
|
||||
@ -241,15 +216,18 @@ struct noop_deserialize
|
||||
|
||||
// Helper to serialize top-level messages
|
||||
template <class Serializable, class Allocator>
|
||||
void serialize_message(
|
||||
const Serializable& input,
|
||||
capabilities caps,
|
||||
basic_bytestring<Allocator>& buffer
|
||||
);
|
||||
void serialize_message(const Serializable& input, capabilities caps, basic_bytestring<Allocator>& buffer);
|
||||
|
||||
template <class Deserializable>
|
||||
error_code deserialize_message(deserialization_context& ctx, Deserializable& output);
|
||||
|
||||
template <class Deserializable>
|
||||
error_code deserialize_message(boost::asio::const_buffer msg, Deserializable& output, capabilities caps)
|
||||
{
|
||||
deserialization_context ctx(msg, caps);
|
||||
return deserialize_message(ctx, output);
|
||||
}
|
||||
|
||||
template <class Deserializable>
|
||||
error_code deserialize_message_part(deserialization_context& ctx, Deserializable& output);
|
||||
|
||||
|
32
include/boost/mysql/detail/typing/get_type_index.hpp
Normal file
32
include/boost/mysql/detail/typing/get_type_index.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_GET_TYPE_INDEX_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_GET_TYPE_INDEX_HPP
|
||||
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
constexpr std::size_t index_not_found = static_cast<std::size_t>(-1);
|
||||
|
||||
template <class SpanRowType, class... RowType>
|
||||
constexpr std::size_t get_type_index() noexcept
|
||||
{
|
||||
using lunique = mp11::mp_unique<mp11::mp_list<RowType...>>;
|
||||
using index_t = mp11::mp_find<lunique, SpanRowType>;
|
||||
return index_t::value < mp11::mp_size<lunique>::value ? index_t::value : index_not_found;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
158
include/boost/mysql/detail/typing/meta_check_context.hpp
Normal file
158
include/boost/mysql/detail/typing/meta_check_context.hpp
Normal file
@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_META_CHECK_CONTEXT_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_META_CHECK_CONTEXT_HPP
|
||||
|
||||
#include <boost/mysql/column_type.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/typing/pos_map.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline const char* column_type_to_str(const metadata& meta) noexcept
|
||||
{
|
||||
switch (meta.type())
|
||||
{
|
||||
case column_type::tinyint: return meta.is_unsigned() ? "TINYINT UNSIGNED" : "TINYINT";
|
||||
case column_type::smallint: return meta.is_unsigned() ? "SMALLINT UNSIGNED" : "SMALLINT";
|
||||
case column_type::mediumint: return meta.is_unsigned() ? "MEDIUMINT UNSIGNED" : "MEDIUMINT";
|
||||
case column_type::int_: return meta.is_unsigned() ? "INT UNSIGNED" : "INT";
|
||||
case column_type::bigint: return meta.is_unsigned() ? "BIGINT UNSIGNED" : "BIGINT";
|
||||
case column_type::float_: return "FLOAT";
|
||||
case column_type::double_: return "DOUBLE";
|
||||
case column_type::decimal: return "DECIMAL";
|
||||
case column_type::bit: return "BIT";
|
||||
case column_type::year: return "YEAR";
|
||||
case column_type::time: return "TIME";
|
||||
case column_type::date: return "DATE";
|
||||
case column_type::datetime: return "DATETIME";
|
||||
case column_type::timestamp: return "TIMESTAMP";
|
||||
case column_type::char_: return "CHAR";
|
||||
case column_type::varchar: return "VARCHAR";
|
||||
case column_type::binary: return "BINARY";
|
||||
case column_type::varbinary: return "VARBINARY";
|
||||
case column_type::text: return "TEXT";
|
||||
case column_type::blob: return "BLOB";
|
||||
case column_type::enum_: return "ENUM";
|
||||
case column_type::set: return "SET";
|
||||
case column_type::json: return "JSON";
|
||||
case column_type::geometry: return "GEOMETRY";
|
||||
default: return "<unknown column type>";
|
||||
}
|
||||
}
|
||||
|
||||
class meta_check_context
|
||||
{
|
||||
std::unique_ptr<std::ostringstream> errors_;
|
||||
std::size_t current_index_{};
|
||||
span<const std::size_t> pos_map_;
|
||||
name_table_t name_table_;
|
||||
metadata_collection_view meta_{};
|
||||
bool nullability_checked_{};
|
||||
|
||||
std::ostringstream& add_error()
|
||||
{
|
||||
if (!errors_)
|
||||
errors_.reset(new std::ostringstream);
|
||||
else
|
||||
*errors_ << '\n';
|
||||
return *errors_;
|
||||
}
|
||||
|
||||
void insert_field_name(std::ostringstream& os)
|
||||
{
|
||||
if (has_field_names(name_table_))
|
||||
os << "'" << name_table_[current_index_] << "'";
|
||||
else
|
||||
os << "in position " << current_index_;
|
||||
}
|
||||
|
||||
public:
|
||||
meta_check_context(
|
||||
span<const std::size_t> pos_map,
|
||||
name_table_t name_table,
|
||||
metadata_collection_view meta
|
||||
) noexcept
|
||||
: pos_map_(pos_map), name_table_(name_table), meta_(meta)
|
||||
{
|
||||
}
|
||||
|
||||
// Accessors
|
||||
const metadata& current_meta() const noexcept { return map_metadata(pos_map_, current_index_, meta_); }
|
||||
bool is_current_field_absent() const noexcept { return pos_map_[current_index_] == pos_absent; }
|
||||
|
||||
// Iteration
|
||||
void advance() noexcept
|
||||
{
|
||||
nullability_checked_ = false;
|
||||
++current_index_;
|
||||
}
|
||||
|
||||
// Nullability
|
||||
void set_nullability_checked() noexcept { nullability_checked_ = true; }
|
||||
bool nullability_checked() const noexcept { return nullability_checked_; }
|
||||
|
||||
// Error reporting
|
||||
void add_field_absent_error()
|
||||
{
|
||||
auto& stream = add_error();
|
||||
stream << "Field ";
|
||||
insert_field_name(stream);
|
||||
if (has_field_names(name_table_))
|
||||
{
|
||||
stream << " is not present in the data returned by the server";
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << " can't be mapped: there are more fields in your C++ data type than in your query";
|
||||
}
|
||||
}
|
||||
|
||||
void add_type_mismatch_error(const char* cpp_type_name)
|
||||
{
|
||||
auto& stream = add_error();
|
||||
stream << "Incompatible types for field ";
|
||||
insert_field_name(stream);
|
||||
stream << ": C++ type '" << cpp_type_name << "' is not compatible with DB type '"
|
||||
<< column_type_to_str(current_meta()) << "'";
|
||||
}
|
||||
|
||||
void add_nullability_error()
|
||||
{
|
||||
auto& stream = add_error();
|
||||
stream << "NULL checks failed for field ";
|
||||
insert_field_name(stream);
|
||||
stream << ": the database type may be NULL, but the C++ type cannot. Use std::optional<T> or "
|
||||
"boost::optional<T>";
|
||||
}
|
||||
|
||||
error_code check_errors(diagnostics& diag) const
|
||||
{
|
||||
if (errors_ != nullptr)
|
||||
{
|
||||
diagnostics_access::assign_client(diag, errors_->str());
|
||||
return client_errc::metadata_check_failed;
|
||||
}
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
91
include/boost/mysql/detail/typing/pos_map.hpp
Normal file
91
include/boost/mysql/detail/typing/pos_map.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_POS_MAP_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_POS_MAP_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// These functions map C++ type positions to positions to positions in the DB query
|
||||
|
||||
constexpr std::size_t pos_absent = static_cast<std::size_t>(-1);
|
||||
using name_table_t = boost::span<const string_view>;
|
||||
|
||||
inline bool has_field_names(name_table_t name_table) noexcept { return !name_table.empty(); }
|
||||
|
||||
inline void pos_map_reset(span<std::size_t> self) noexcept
|
||||
{
|
||||
for (std::size_t i = 0; i < self.size(); ++i)
|
||||
self.data()[i] = pos_absent;
|
||||
}
|
||||
|
||||
inline void pos_map_add_field(
|
||||
span<std::size_t> self,
|
||||
name_table_t name_table,
|
||||
std::size_t db_index,
|
||||
string_view field_name
|
||||
) noexcept
|
||||
{
|
||||
if (has_field_names(name_table))
|
||||
{
|
||||
assert(self.size() == name_table.size());
|
||||
|
||||
// We're mapping fields by name. Try to find where in our target struct
|
||||
// is the current field located
|
||||
auto it = std::find(name_table.begin(), name_table.end(), field_name);
|
||||
if (it != name_table.end())
|
||||
{
|
||||
std::size_t cpp_index = it - name_table.begin();
|
||||
self[cpp_index] = db_index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're mapping by position. Any extra trailing fields are discarded
|
||||
if (db_index < self.size())
|
||||
{
|
||||
self[db_index] = db_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline field_view map_field_view(
|
||||
span<const std::size_t> self,
|
||||
std::size_t cpp_index,
|
||||
span<const field_view> array
|
||||
) noexcept
|
||||
{
|
||||
assert(cpp_index < self.size());
|
||||
return array[self[cpp_index]];
|
||||
}
|
||||
|
||||
inline const metadata& map_metadata(
|
||||
span<const std::size_t> self,
|
||||
std::size_t cpp_index,
|
||||
metadata_collection_view meta
|
||||
) noexcept
|
||||
{
|
||||
assert(cpp_index < self.size());
|
||||
return meta[self[cpp_index]];
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
620
include/boost/mysql/detail/typing/readable_field_traits.hpp
Normal file
620
include/boost/mysql/detail/typing/readable_field_traits.hpp
Normal file
@ -0,0 +1,620 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_READABLE_FIELD_TRAITS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_READABLE_FIELD_TRAITS_HPP
|
||||
|
||||
#include <boost/mysql/date.hpp>
|
||||
#include <boost/mysql/datetime.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_kind.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/time.hpp>
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
#include <boost/mysql/detail/typing/meta_check_context.hpp>
|
||||
#include <boost/mysql/detail/typing/pos_map.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Helpers for integers
|
||||
template <class SignedInt>
|
||||
error_code parse_signed_int(field_view input, SignedInt& output)
|
||||
{
|
||||
using unsigned_t = typename std::make_unsigned<SignedInt>::type;
|
||||
using limits_t = std::numeric_limits<SignedInt>;
|
||||
|
||||
auto kind = input.kind();
|
||||
if (kind == field_kind::int64)
|
||||
{
|
||||
auto v = input.get_int64();
|
||||
if (v < (limits_t::min)() || v > (limits_t::max)())
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = static_cast<SignedInt>(v);
|
||||
return error_code();
|
||||
}
|
||||
else if (kind == field_kind::uint64)
|
||||
{
|
||||
auto v = input.get_uint64();
|
||||
if (v > static_cast<unsigned_t>((limits_t::max)()))
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = static_cast<SignedInt>(v);
|
||||
return error_code();
|
||||
}
|
||||
else
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
}
|
||||
|
||||
template <class UnsignedInt>
|
||||
error_code parse_unsigned_int(field_view input, UnsignedInt& output)
|
||||
{
|
||||
if (input.kind() != field_kind::uint64)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
auto v = input.get_uint64();
|
||||
if (v > (std::numeric_limits<UnsignedInt>::max)())
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = static_cast<UnsignedInt>(v);
|
||||
return error_code();
|
||||
}
|
||||
|
||||
// We want all integer types to be allowed as fields. Some integers
|
||||
// may have the same width as others, but different type (e.g. long and long long
|
||||
// may both be 64-bit, but different types). Auxiliar int_traits to allow this to work
|
||||
template <class T, bool is_signed = std::is_signed<T>::value, std::size_t width = sizeof(T)>
|
||||
struct int_traits
|
||||
{
|
||||
static constexpr bool is_supported = false;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, true, 1>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "int8_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint: return !ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, false, 1>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "uint8_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint: return ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, true, 2>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "int16_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint: return true;
|
||||
case column_type::smallint:
|
||||
case column_type::year: return !ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, false, 2>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "uint16_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint:
|
||||
case column_type::smallint:
|
||||
case column_type::year: return ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, true, 4>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "int32_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint:
|
||||
case column_type::smallint:
|
||||
case column_type::year:
|
||||
case column_type::mediumint: return true;
|
||||
case column_type::int_: return !ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, false, 4>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "uint32_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint:
|
||||
case column_type::smallint:
|
||||
case column_type::year:
|
||||
case column_type::mediumint:
|
||||
case column_type::int_: return ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_unsigned_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, true, 8>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "int64_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint:
|
||||
case column_type::smallint:
|
||||
case column_type::year:
|
||||
case column_type::mediumint:
|
||||
case column_type::int_: return true;
|
||||
case column_type::bigint: return !ctx.current_meta().is_unsigned();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, T& output) { return parse_signed_int(input, output); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct int_traits<T, false, 8>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "uint64_t";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::tinyint:
|
||||
case column_type::smallint:
|
||||
case column_type::year:
|
||||
case column_type::mediumint:
|
||||
case column_type::int_:
|
||||
case column_type::bigint: return ctx.current_meta().is_unsigned();
|
||||
case column_type::bit: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, std::uint64_t& output)
|
||||
{
|
||||
return parse_unsigned_int(input, output);
|
||||
}
|
||||
};
|
||||
|
||||
// Traits
|
||||
template <typename T, class EnableIf = void>
|
||||
struct readable_field_traits
|
||||
{
|
||||
static constexpr bool is_supported = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<char, void> : int_traits<char>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<signed char, void> : int_traits<signed char>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<unsigned char, void> : int_traits<unsigned char>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<short, void> : int_traits<short>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<unsigned short, void> : int_traits<unsigned short>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<int, void> : int_traits<int>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<unsigned int, void> : int_traits<unsigned int>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<long, void> : int_traits<long>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<unsigned long, void> : int_traits<unsigned long>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<long long, void> : int_traits<long long>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<unsigned long long, void> : int_traits<unsigned long long>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<bool, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "bool";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
return ctx.current_meta().type() == column_type::tinyint && !ctx.current_meta().is_unsigned();
|
||||
}
|
||||
static error_code parse(field_view input, bool& output)
|
||||
{
|
||||
if (input.kind() != field_kind::int64)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_int64() != 0;
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<float, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "float";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
return ctx.current_meta().type() == column_type::float_;
|
||||
}
|
||||
static error_code parse(field_view input, float& output)
|
||||
{
|
||||
if (input.kind() != field_kind::float_)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_float();
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<double, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "double";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::float_:
|
||||
case column_type::double_: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, double& output)
|
||||
{
|
||||
auto kind = input.kind();
|
||||
if (kind == field_kind::float_)
|
||||
{
|
||||
output = input.get_float();
|
||||
return error_code();
|
||||
}
|
||||
else if (kind == field_kind::double_)
|
||||
{
|
||||
output = input.get_double();
|
||||
return error_code();
|
||||
}
|
||||
else
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct readable_field_traits<std::basic_string<char, std::char_traits<char>, Allocator>, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "string";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::decimal:
|
||||
case column_type::char_:
|
||||
case column_type::varchar:
|
||||
case column_type::text:
|
||||
case column_type::enum_:
|
||||
case column_type::set:
|
||||
case column_type::json: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(
|
||||
field_view input,
|
||||
std::basic_string<char, std::char_traits<char>, Allocator>& output
|
||||
)
|
||||
{
|
||||
if (input.kind() != field_kind::string)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_string();
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct readable_field_traits<std::vector<unsigned char, Allocator>, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "blob";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::binary:
|
||||
case column_type::varbinary:
|
||||
case column_type::blob:
|
||||
case column_type::geometry:
|
||||
case column_type::unknown: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, std::vector<unsigned char, Allocator>& output)
|
||||
{
|
||||
if (input.kind() != field_kind::blob)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
auto view = input.get_blob();
|
||||
output.assign(view.begin(), view.end());
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<date, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "date";
|
||||
static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::date; }
|
||||
static error_code parse(field_view input, date& output)
|
||||
{
|
||||
if (input.kind() != field_kind::date)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_date();
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<datetime, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "datetime";
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
switch (ctx.current_meta().type())
|
||||
{
|
||||
case column_type::datetime:
|
||||
case column_type::timestamp: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
static error_code parse(field_view input, datetime& output)
|
||||
{
|
||||
if (input.kind() != field_kind::datetime)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_datetime();
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct readable_field_traits<time, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = "time";
|
||||
static bool meta_check(meta_check_context& ctx) { return ctx.current_meta().type() == column_type::time; }
|
||||
static error_code parse(field_view input, time& output)
|
||||
{
|
||||
if (input.kind() != field_kind::time)
|
||||
{
|
||||
return client_errc::static_row_parsing_error;
|
||||
}
|
||||
output = input.get_time();
|
||||
return error_code();
|
||||
}
|
||||
};
|
||||
|
||||
// std::optional<T> and boost::optional<T>. To avoid dependencies,
|
||||
// this is achieved through a "concept"
|
||||
template <class T, class = void>
|
||||
struct is_readable_optional : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_readable_optional<
|
||||
T,
|
||||
void_t<
|
||||
typename std::enable_if<
|
||||
std::is_same<decltype(std::declval<T&>().value()), typename T::value_type&>::value>::type,
|
||||
decltype(std::declval<T&>().emplace()), // T should be default constructible
|
||||
decltype(std::declval<T&>().reset())>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct readable_field_traits<
|
||||
T,
|
||||
typename std::enable_if<
|
||||
is_readable_optional<T>::value && readable_field_traits<typename T::value_type>::is_supported>::type>
|
||||
{
|
||||
using value_type = typename T::value_type;
|
||||
static constexpr bool is_supported = true;
|
||||
static constexpr const char* type_name = readable_field_traits<value_type>::type_name;
|
||||
static bool meta_check(meta_check_context& ctx)
|
||||
{
|
||||
ctx.set_nullability_checked();
|
||||
return readable_field_traits<value_type>::meta_check(ctx);
|
||||
}
|
||||
static error_code parse(field_view input, T& output)
|
||||
{
|
||||
if (input.is_null())
|
||||
{
|
||||
output.reset();
|
||||
return error_code();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.emplace();
|
||||
return readable_field_traits<value_type>::parse(input, output.value());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_readable_field
|
||||
{
|
||||
static constexpr bool value = readable_field_traits<T>::is_supported;
|
||||
};
|
||||
|
||||
template <typename ReadableField>
|
||||
void meta_check_field_impl(meta_check_context& ctx)
|
||||
{
|
||||
using traits_t = readable_field_traits<ReadableField>;
|
||||
|
||||
// Verify that the field is present
|
||||
if (ctx.is_current_field_absent())
|
||||
{
|
||||
ctx.add_field_absent_error();
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform the check
|
||||
bool ok = traits_t::meta_check(ctx);
|
||||
if (!ok)
|
||||
{
|
||||
ctx.add_type_mismatch_error(traits_t::type_name);
|
||||
}
|
||||
|
||||
// Check nullability
|
||||
if (!ctx.nullability_checked() && !ctx.current_meta().is_not_null())
|
||||
{
|
||||
ctx.add_nullability_error();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ReadableField>
|
||||
void meta_check_field(meta_check_context& ctx)
|
||||
{
|
||||
static_assert(is_readable_field<ReadableField>::value, "Should be a ReadableField");
|
||||
meta_check_field_impl<ReadableField>(ctx);
|
||||
ctx.advance();
|
||||
}
|
||||
|
||||
struct meta_check_field_fn
|
||||
{
|
||||
meta_check_context ctx;
|
||||
|
||||
template <class T>
|
||||
void operator()(T)
|
||||
{
|
||||
meta_check_field<typename T::type>(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ReadableFieldList>
|
||||
error_code meta_check_field_type_list(
|
||||
span<const std::size_t> field_map,
|
||||
name_table_t name_table,
|
||||
metadata_collection_view meta,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
meta_check_field_fn fn{meta_check_context(field_map, name_table, meta)};
|
||||
boost::mp11::mp_for_each<ReadableFieldList>(fn);
|
||||
return fn.ctx.check_errors(diag);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
268
include/boost/mysql/detail/typing/row_traits.hpp
Normal file
268
include/boost/mysql/detail/typing/row_traits.hpp
Normal file
@ -0,0 +1,268 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_ROW_TRAITS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_ROW_TRAITS_HPP
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
|
||||
#include <boost/mysql/client_errc.hpp>
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
#include <boost/mysql/detail/typing/meta_check_context.hpp>
|
||||
#include <boost/mysql/detail/typing/pos_map.hpp>
|
||||
#include <boost/mysql/detail/typing/readable_field_traits.hpp>
|
||||
|
||||
#include <boost/describe/members.hpp>
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <boost/mp11/utility.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Helpers to check that all the fields satisfy ReadableField
|
||||
// and produce meaningful error messages, with the offending field type, at least
|
||||
|
||||
// Workaround clang 3.6 not liking generic lambdas in the below constexpr function
|
||||
struct readable_field_checker
|
||||
{
|
||||
template <class TypeIdentity>
|
||||
constexpr void operator()(TypeIdentity) const noexcept
|
||||
{
|
||||
using T = typename TypeIdentity::type;
|
||||
static_assert(
|
||||
is_readable_field<T>::value,
|
||||
"You're trying to use an unsupported field type in a row type. Review your row type definitions."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <class TypeList>
|
||||
static constexpr bool check_readable_field() noexcept
|
||||
{
|
||||
mp11::mp_for_each<TypeList>(readable_field_checker{});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Workaround std::array::data not being constexpr in C++14
|
||||
template <class T, std::size_t N>
|
||||
struct array_wrapper
|
||||
{
|
||||
T data_[N];
|
||||
|
||||
constexpr boost::span<const T> span() const noexcept { return boost::span<const T>(data_); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct array_wrapper<T, 0>
|
||||
{
|
||||
struct
|
||||
{
|
||||
} data_; // allow empty brace initialization
|
||||
|
||||
constexpr boost::span<const T> span() const noexcept { return boost::span<const T>(); }
|
||||
};
|
||||
|
||||
// Workaround for char_traits::length not being constexpr in C++14
|
||||
// Only used to retrieve Describe member name lengths
|
||||
constexpr std::size_t get_length(const char* s) noexcept
|
||||
{
|
||||
const char* p = s;
|
||||
while (*p)
|
||||
++p;
|
||||
return p - s;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
class parse_functor
|
||||
{
|
||||
span<const std::size_t> pos_map_;
|
||||
span<const field_view> fields_;
|
||||
std::size_t index_{};
|
||||
error_code ec_;
|
||||
|
||||
public:
|
||||
parse_functor(span<const std::size_t> pos_map, span<const field_view> fields) noexcept
|
||||
: pos_map_(pos_map), fields_(fields)
|
||||
{
|
||||
}
|
||||
|
||||
template <class ReadableField>
|
||||
void operator()(ReadableField& output)
|
||||
{
|
||||
auto ec = readable_field_traits<ReadableField>::parse(
|
||||
map_field_view(pos_map_, index_++, fields_),
|
||||
output
|
||||
);
|
||||
if (!ec_)
|
||||
ec_ = ec;
|
||||
}
|
||||
|
||||
error_code error() const noexcept { return ec_; }
|
||||
};
|
||||
|
||||
// Base template
|
||||
template <class T, bool is_describe_struct = boost::describe::has_describe_members<T>::value>
|
||||
class row_traits;
|
||||
|
||||
// Describe structs
|
||||
template <class DescribeStruct>
|
||||
using row_members = boost::describe::
|
||||
describe_members<DescribeStruct, boost::describe::mod_public | boost::describe::mod_inherited>;
|
||||
|
||||
template <class MemberDescriptor>
|
||||
constexpr string_view get_member_name(MemberDescriptor d) noexcept
|
||||
{
|
||||
return string_view(d.name, get_length(d.name));
|
||||
}
|
||||
|
||||
template <template <class...> class ListType, class... MemberDescriptor>
|
||||
constexpr array_wrapper<string_view, sizeof...(MemberDescriptor)> get_describe_names(ListType<
|
||||
MemberDescriptor...>)
|
||||
{
|
||||
return {{get_member_name(MemberDescriptor())...}};
|
||||
}
|
||||
|
||||
template <class DescribeStruct>
|
||||
constexpr auto describe_names_storage = get_describe_names(row_members<DescribeStruct>{});
|
||||
|
||||
template <class DescribeStruct>
|
||||
class row_traits<DescribeStruct, true>
|
||||
{
|
||||
using members = row_members<DescribeStruct>;
|
||||
|
||||
template <class D>
|
||||
struct descriptor_to_type
|
||||
{
|
||||
using helper = decltype(std::declval<DescribeStruct>().*std::declval<D>().pointer);
|
||||
using type = typename std::remove_reference<helper>::type;
|
||||
};
|
||||
|
||||
using member_types = mp11::mp_transform<descriptor_to_type, members>;
|
||||
|
||||
static_assert(check_readable_field<member_types>(), "");
|
||||
|
||||
public:
|
||||
using types = member_types;
|
||||
|
||||
static constexpr std::size_t size() noexcept { return boost::mp11::mp_size<members>::value; }
|
||||
|
||||
static constexpr name_table_t name_table() noexcept
|
||||
{
|
||||
return describe_names_storage<DescribeStruct>.span();
|
||||
}
|
||||
|
||||
static void parse(parse_functor& parser, DescribeStruct& to)
|
||||
{
|
||||
boost::mp11::mp_for_each<members>([&](auto D) { parser(to.*D.pointer); });
|
||||
}
|
||||
};
|
||||
|
||||
// Tuples
|
||||
template <class T>
|
||||
struct is_tuple : std::false_type
|
||||
{
|
||||
};
|
||||
template <class... T>
|
||||
struct is_tuple<std::tuple<T...>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class... ReadableField>
|
||||
class row_traits<std::tuple<ReadableField...>, false>
|
||||
{
|
||||
using tuple_type = std::tuple<ReadableField...>;
|
||||
using field_types = boost::mp11::mp_list<boost::mp11::mp_identity<ReadableField>...>;
|
||||
|
||||
static_assert(check_readable_field<field_types>(), "");
|
||||
|
||||
public:
|
||||
using types = field_types;
|
||||
static constexpr std::size_t size() noexcept { return std::tuple_size<tuple_type>::value; }
|
||||
static constexpr name_table_t name_table() noexcept { return name_table_t(); }
|
||||
static void parse(parse_functor& parser, tuple_type& to) { boost::mp11::tuple_for_each(to, parser); }
|
||||
};
|
||||
|
||||
// We want is_static_row to only inspect the shape of the row (i.e. it's a tuple vs. it's nothing we know),
|
||||
// and not individual fields. These are static_assert-ed in individual row_traits. This gives us an error
|
||||
// message that contains the offending types, at least.
|
||||
template <class T>
|
||||
struct is_static_row
|
||||
{
|
||||
static constexpr bool value = is_tuple<T>::value || describe::has_describe_members<T>::value;
|
||||
};
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept static_row = is_static_row<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_STATIC_ROW ::boost::mysql::detail::static_row
|
||||
|
||||
#else
|
||||
#define BOOST_MYSQL_STATIC_ROW class
|
||||
#endif
|
||||
|
||||
// External interface
|
||||
template <BOOST_MYSQL_STATIC_ROW StaticRow>
|
||||
constexpr std::size_t get_row_size()
|
||||
{
|
||||
return row_traits<StaticRow>::size();
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_STATIC_ROW StaticRow>
|
||||
constexpr name_table_t get_row_name_table()
|
||||
{
|
||||
return row_traits<StaticRow>::name_table();
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_STATIC_ROW StaticRow>
|
||||
error_code meta_check(span<const std::size_t> pos_map, metadata_collection_view meta, diagnostics& diag)
|
||||
{
|
||||
using fields = typename row_traits<StaticRow>::types;
|
||||
assert(pos_map.size() == get_row_size<StaticRow>());
|
||||
return meta_check_field_type_list<fields>(pos_map, get_row_name_table<StaticRow>(), meta, diag);
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_STATIC_ROW StaticRow>
|
||||
error_code parse(span<const std::size_t> pos_map, span<const field_view> from, StaticRow& to)
|
||||
{
|
||||
assert(pos_map.size() == get_row_size<StaticRow>());
|
||||
assert(from.size() >= get_row_size<StaticRow>());
|
||||
parse_functor ctx(pos_map, from);
|
||||
row_traits<StaticRow>::parse(ctx, to);
|
||||
return ctx.error();
|
||||
}
|
||||
|
||||
using meta_check_fn_t =
|
||||
error_code (*)(span<const std::size_t> field_map, metadata_collection_view meta, diagnostics& diag);
|
||||
|
||||
// For multi-resultset - helper
|
||||
template <class... StaticRow>
|
||||
constexpr std::size_t max_num_columns = (std::max)({get_row_size<StaticRow>()...});
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_MYSQL_CXX14
|
||||
|
||||
#endif
|
149
include/boost/mysql/detail/typing/writable_field_traits.hpp
Normal file
149
include/boost/mysql/detail/typing/writable_field_traits.hpp
Normal file
@ -0,0 +1,149 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_TYPING_WRITABLE_FIELD_TRAITS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_TYPING_WRITABLE_FIELD_TRAITS_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/void_t.hpp>
|
||||
#include <boost/mysql/detail/config.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class T, class En1 = void, class En2 = void>
|
||||
struct writable_field_traits
|
||||
{
|
||||
static constexpr bool is_supported = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct writable_field_traits<bool, void, void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static field_view to_field(bool value) noexcept { return field_view(value ? 1 : 0); }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct writable_field_traits<
|
||||
T,
|
||||
typename std::enable_if<std::is_constructible<field_view, const T&>::value>::type,
|
||||
void>
|
||||
{
|
||||
static constexpr bool is_supported = true;
|
||||
static field_view to_field(const T& value) noexcept { return field_view(value); }
|
||||
};
|
||||
|
||||
// Optionals. To avoid dependencies, we use a "concept".
|
||||
// We consider a type an optional if has a `bool has_value() const` and
|
||||
// `const value_type& value() const`
|
||||
template <class T>
|
||||
struct writable_field_traits<
|
||||
T,
|
||||
void,
|
||||
typename std::enable_if<
|
||||
std::is_same<decltype(std::declval<const T&>().has_value()), bool>::value &&
|
||||
std::is_same<decltype(std::declval<const T&>().value()), const typename T::value_type&>::value>::type>
|
||||
{
|
||||
using value_traits = writable_field_traits<typename T::value_type>;
|
||||
static constexpr bool is_supported = value_traits::is_supported;
|
||||
static field_view to_field(const T& value) noexcept
|
||||
{
|
||||
return value.has_value() ? value_traits::to_field(value.value()) : field_view();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
field_view to_field(const T& value) noexcept
|
||||
{
|
||||
return writable_field_traits<T>::to_field(value);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct is_writable_field
|
||||
{
|
||||
static constexpr bool value = writable_field_traits<T>::is_supported;
|
||||
};
|
||||
|
||||
// field_view_forward_iterator
|
||||
template <typename T, typename = void>
|
||||
struct is_field_view_forward_iterator : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
struct is_field_view_forward_iterator<T, void_t<
|
||||
typename std::enable_if<
|
||||
std::is_convertible<
|
||||
typename std::iterator_traits<T>::reference,
|
||||
field_view
|
||||
>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
std::forward_iterator_tag,
|
||||
typename std::iterator_traits<T>::iterator_category
|
||||
>::value
|
||||
>::type
|
||||
>> : std::true_type { };
|
||||
// clang-format on
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept field_view_forward_iterator = is_field_view_forward_iterator<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR ::boost::mysql::detail::field_view_forward_iterator
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
// writable_field_tuple
|
||||
template <class... T>
|
||||
struct is_writable_field_tuple_impl : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class... T>
|
||||
struct is_writable_field_tuple_impl<std::tuple<T...>>
|
||||
: mp11::mp_all_of<mp11::mp_list<T...>, is_writable_field>
|
||||
{
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
struct is_writable_field_tuple : is_writable_field_tuple_impl<typename std::decay<Tuple>::type>
|
||||
{
|
||||
};
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept writable_field_tuple = is_writable_field_tuple<T>::value;
|
||||
|
||||
#define BOOST_MYSQL_WRITABLE_FIELD_TUPLE ::boost::mysql::detail::writable_field_tuple
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_WRITABLE_FIELD_TUPLE class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -22,19 +22,35 @@ namespace mysql {
|
||||
* \brief Contains additional information about errors.
|
||||
* \details
|
||||
* This class is a container for additional diagnostics about an operation that
|
||||
* failed. Currently, it's used to hold any error messages sent by the server on
|
||||
* error (\ref server_message). More members may be added in the future.
|
||||
* failed. It can contain server-generated messages (\ref server_message) or client-side messages
|
||||
* (\ref client_message). More members may be added in the future.
|
||||
*/
|
||||
class diagnostics
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs a diagnostics object with an empty error message.
|
||||
* \brief Constructs a diagnostics object with empty error messages.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
diagnostics() = default;
|
||||
|
||||
/**
|
||||
* \brief Gets the client-generated error message.
|
||||
* \details
|
||||
* Contrary to \ref server_message, the client message never contains any string data
|
||||
* returned by the server, and is always ASCII-encoded. If you're using the static interface,
|
||||
* it may contain C++ type identifiers, too.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned view is valid as long as `*this` is alive, hasn't been assigned-to
|
||||
* or moved-from, and \ref clear hasn't been called. Moving `*this` invalidates the view.
|
||||
*/
|
||||
string_view client_message() const noexcept { return is_server_ ? string_view() : string_view(msg_); }
|
||||
|
||||
/**
|
||||
* \brief Gets the server-generated error message.
|
||||
* \details
|
||||
@ -48,19 +64,25 @@ public:
|
||||
* The returned view is valid as long as `*this` is alive, hasn't been assigned-to
|
||||
* or moved-from, and \ref clear hasn't been called. Moving `*this` invalidates the view.
|
||||
*/
|
||||
string_view server_message() const noexcept { return msg_; }
|
||||
string_view server_message() const noexcept { return is_server_ ? string_view(msg_) : string_view(); }
|
||||
|
||||
/**
|
||||
* \brief Clears the error message.
|
||||
* \brief Clears the error messages.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
void clear() noexcept { msg_.clear(); }
|
||||
void clear() noexcept
|
||||
{
|
||||
is_server_ = false;
|
||||
msg_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_server_{};
|
||||
std::string msg_;
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend bool operator==(const diagnostics& lhs, const diagnostics& rhs) noexcept;
|
||||
friend struct detail::diagnostics_access;
|
||||
#endif
|
||||
};
|
||||
@ -73,7 +95,7 @@ private:
|
||||
*/
|
||||
inline bool operator==(const diagnostics& lhs, const diagnostics& rhs) noexcept
|
||||
{
|
||||
return lhs.server_message() == rhs.server_message();
|
||||
return lhs.is_server_ == rhs.is_server_ && lhs.msg_ == rhs.msg_;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,10 +26,16 @@ class error_with_diagnostics : public boost::system::system_error
|
||||
{
|
||||
diagnostics diag_;
|
||||
|
||||
static boost::system::system_error create_base(const error_code& err, const diagnostics& diag)
|
||||
{
|
||||
return diag.client_message().empty() ? boost::system::system_error(err)
|
||||
: boost::system::system_error(err, diag.client_message());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Initializing constructor.
|
||||
error_with_diagnostics(const error_code& err, const diagnostics& diag)
|
||||
: boost::system::system_error(err), diag_(diag)
|
||||
: boost::system::system_error(create_base(err, diag)), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,7 @@
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
@ -26,7 +24,7 @@ namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief Holds state for multi-function SQL execution operations.
|
||||
* \brief Holds state for multi-function SQL execution operations (dynamic interface).
|
||||
* \details
|
||||
* This class behaves like a state machine. The current state can be accessed using
|
||||
* \ref should_start_op, \ref should_read_rows, \ref should_read_head
|
||||
@ -48,7 +46,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
execution_state() noexcept : impl_(false){};
|
||||
execution_state() = default;
|
||||
|
||||
/**
|
||||
* \brief Copy constructor.
|
||||
@ -100,7 +98,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool should_start_op() const noexcept { return impl_.num_resultsets() == 0u; }
|
||||
bool should_start_op() const noexcept { return impl_.is_reading_first(); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the next operation should be read resultset head.
|
||||
@ -111,10 +109,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool should_read_head() const noexcept
|
||||
{
|
||||
return impl_.num_resultsets() == 1u && (impl_.should_read_head() || impl_.should_read_meta());
|
||||
}
|
||||
bool should_read_head() const noexcept { return impl_.is_reading_first_subseq(); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the next operation should be read some rows.
|
||||
@ -125,7 +120,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool should_read_rows() const noexcept { return impl_.should_read_rows(); }
|
||||
bool should_read_rows() const noexcept { return impl_.is_reading_rows(); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether all the messages generated by this operation have been read.
|
||||
@ -136,7 +131,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool complete() const noexcept { return impl_.complete(); }
|
||||
bool complete() const noexcept { return impl_.is_complete(); }
|
||||
|
||||
/**
|
||||
* \brief Returns metadata about the columns in the query.
|
||||
@ -152,10 +147,7 @@ public:
|
||||
* memory owned by `*this`, and will be valid as long as `*this` or an object move-constructed
|
||||
* from `*this` are alive.
|
||||
*/
|
||||
metadata_collection_view meta() const noexcept
|
||||
{
|
||||
return impl_.num_resultsets() == 0u ? metadata_collection_view() : impl_.get_meta(0);
|
||||
}
|
||||
metadata_collection_view meta() const noexcept { return impl_.meta(); }
|
||||
|
||||
/**
|
||||
* \brief Returns the number of rows affected by the SQL statement associated to this resultset.
|
||||
@ -165,7 +157,7 @@ public:
|
||||
* \par Preconditions
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
std::uint64_t affected_rows() const noexcept { return impl_.get_affected_rows(0); }
|
||||
std::uint64_t affected_rows() const noexcept { return impl_.get_affected_rows(); }
|
||||
|
||||
/**
|
||||
* \brief Returns the last insert ID produced by the SQL statement associated to this resultset.
|
||||
@ -175,7 +167,7 @@ public:
|
||||
* \par Preconditions
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
std::uint64_t last_insert_id() const noexcept { return impl_.get_last_insert_id(0); }
|
||||
std::uint64_t last_insert_id() const noexcept { return impl_.get_last_insert_id(); }
|
||||
|
||||
/**
|
||||
* \brief Returns the number of warnings produced by the SQL statement associated to this resultset.
|
||||
@ -185,10 +177,10 @@ public:
|
||||
* \par Preconditions
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
unsigned warning_count() const noexcept { return impl_.get_warning_count(0); }
|
||||
unsigned warning_count() const noexcept { return impl_.get_warning_count(); }
|
||||
|
||||
/**
|
||||
* \brief Returns additionat text information about this resultset.
|
||||
* \brief Returns additional text information about this resultset.
|
||||
* \details
|
||||
* The format of this information is documented by MySQL <a
|
||||
* href="https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html">here</a>.
|
||||
@ -206,7 +198,7 @@ public:
|
||||
* memory owned by `*this`, and will be valid as long as `*this` or an object move-constructed
|
||||
* from `*this` are alive.
|
||||
*/
|
||||
string_view info() const noexcept { return impl_.get_info(0); }
|
||||
string_view info() const noexcept { return impl_.get_info(); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the current resultset represents a procedure OUT params.
|
||||
@ -216,18 +208,16 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool is_out_params() const noexcept { return impl_.get_is_out_params(0); }
|
||||
bool is_out_params() const noexcept { return impl_.get_is_out_params(); }
|
||||
|
||||
private:
|
||||
detail::execution_state_impl impl_;
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend struct detail::execution_state_access;
|
||||
friend struct detail::impl_access;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include <boost/mysql/impl/execution_state.hpp>
|
||||
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
|
@ -27,7 +27,9 @@
|
||||
#include <boost/mysql/detail/network_algorithms/prepare_statement.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/quit_connection.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_dynamic.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_impl.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_some_rows_static.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/assert/source_location.hpp>
|
||||
@ -113,14 +115,20 @@ void boost::mysql::connection<Stream>::start_query(
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::start_execution(get_channel(), query_string, result, err, diag);
|
||||
detail::start_execution(get_channel(), query_string, detail::impl_access::get_impl(result), err, diag);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::start_query(string_view query_string, execution_state& result)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::start_execution(get_channel(), query_string, result, blk.err, blk.diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
query_string,
|
||||
detail::impl_access::get_impl(result),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
@ -137,7 +145,7 @@ boost::mysql::connection<Stream>::async_start_query(
|
||||
return detail::async_start_execution(
|
||||
get_channel(),
|
||||
query_string,
|
||||
result,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
@ -152,14 +160,26 @@ void boost::mysql::connection<Stream>::query(
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::execute(get_channel(), query_string, result, err, diag);
|
||||
detail::execute(
|
||||
get_channel(),
|
||||
query_string,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::query(string_view query_string, results& result)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::execute(get_channel(), query_string, result, blk.err, blk.diag);
|
||||
detail::execute(
|
||||
get_channel(),
|
||||
query_string,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
@ -176,42 +196,49 @@ boost::mysql::connection<Stream>::async_query(
|
||||
return detail::async_execute(
|
||||
get_channel(),
|
||||
query_string,
|
||||
result,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void boost::mysql::connection<Stream>::execute(
|
||||
const ExecutionRequest& req,
|
||||
results& result,
|
||||
ResultsType& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::execute(get_channel(), req, result, err, diag);
|
||||
detail::execute(get_channel(), req, detail::impl_access::get_impl(result).get_interface(), err, diag);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void boost::mysql::connection<Stream>::execute(const ExecutionRequest& req, results& result)
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_RESULTS_TYPE ResultsType>
|
||||
void boost::mysql::connection<Stream>::execute(const ExecutionRequest& req, ResultsType& result)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::execute(get_channel(), req, result, blk.err, blk.diag);
|
||||
detail::execute(
|
||||
get_channel(),
|
||||
req,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_RESULTS_TYPE ResultsType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::connection<Stream>::async_execute(
|
||||
ExecutionRequest&& req,
|
||||
results& result,
|
||||
ResultsType& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
@ -219,42 +246,49 @@ boost::mysql::connection<Stream>::async_execute(
|
||||
return detail::async_execute(
|
||||
get_channel(),
|
||||
std::forward<ExecutionRequest>(req),
|
||||
result,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void boost::mysql::connection<Stream>::start_execution(
|
||||
const ExecutionRequest& req,
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::start_execution(get_channel(), req, st, err, diag);
|
||||
detail::start_execution(get_channel(), req, detail::impl_access::get_impl(st).get_interface(), err, diag);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest>
|
||||
void boost::mysql::connection<Stream>::start_execution(const ExecutionRequest& req, execution_state& st)
|
||||
template <BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void boost::mysql::connection<Stream>::start_execution(const ExecutionRequest& req, ExecutionStateType& st)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::start_execution(get_channel(), req, st, blk.err, blk.diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
req,
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest,
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::connection<Stream>::async_start_execution(
|
||||
ExecutionRequest&& req,
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
@ -262,7 +296,7 @@ boost::mysql::connection<Stream>::async_start_execution(
|
||||
return detail::async_start_execution(
|
||||
get_channel(),
|
||||
std::forward<ExecutionRequest>(req),
|
||||
st,
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
@ -304,41 +338,53 @@ boost::mysql::connection<Stream>::async_prepare_statement(
|
||||
|
||||
// execute statement
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple, class>
|
||||
void boost::mysql::connection<Stream>::execute_statement(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
results& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::execute(get_channel(), stmt.bind(params), result, err, diag);
|
||||
detail::execute(
|
||||
get_channel(),
|
||||
stmt.bind(params),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple, class>
|
||||
void boost::mysql::connection<Stream>::execute_statement(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
results& result
|
||||
)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::execute(get_channel(), stmt.bind(params), result, blk.err, blk.diag);
|
||||
detail::execute(
|
||||
get_channel(),
|
||||
stmt.bind(params),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken,
|
||||
class>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::connection<Stream>::async_execute_statement(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
results& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
@ -346,49 +392,61 @@ boost::mysql::connection<Stream>::async_execute_statement(
|
||||
{
|
||||
return detail::async_execute(
|
||||
get_channel(),
|
||||
stmt.bind(std::forward<FieldLikeTuple>(params)),
|
||||
result,
|
||||
stmt.bind(std::forward<WritableFieldTuple>(params)),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple, class>
|
||||
void boost::mysql::connection<Stream>::start_statement_execution(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
execution_state& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::start_execution(get_channel(), stmt.bind(params), result, err, diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(params),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
|
||||
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple, class>
|
||||
void boost::mysql::connection<Stream>::start_statement_execution(
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
const WritableFieldTuple& params,
|
||||
execution_state& result
|
||||
)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::start_execution(get_channel(), stmt.bind(params), result, blk.err, blk.diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(params),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken,
|
||||
class>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::connection<Stream>::async_start_statement_execution(
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
WritableFieldTuple&& params,
|
||||
execution_state& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
@ -396,8 +454,8 @@ boost::mysql::connection<Stream>::async_start_statement_execution(
|
||||
{
|
||||
return detail::async_start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(std::forward<FieldLikeTuple>(params)),
|
||||
result,
|
||||
stmt.bind(std::forward<WritableFieldTuple>(params)),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
@ -410,13 +468,19 @@ void boost::mysql::connection<Stream>::start_statement_execution(
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last,
|
||||
execution_state& result,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
detail::start_execution(get_channel(), stmt.bind(params_first, params_last), result, err, diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(params_first, params_last),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
@ -429,7 +493,13 @@ void boost::mysql::connection<Stream>::start_statement_execution(
|
||||
)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::start_execution(get_channel(), stmt.bind(params_first, params_last), result, blk.err, blk.diag);
|
||||
detail::start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(params_first, params_last),
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
@ -450,7 +520,7 @@ boost::mysql::connection<Stream>::async_start_statement_execution(
|
||||
return detail::async_start_execution(
|
||||
get_channel(),
|
||||
stmt.bind(params_first, params_last),
|
||||
result,
|
||||
detail::impl_access::get_impl(result).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
@ -490,8 +560,9 @@ boost::mysql::connection<Stream>::async_close_statement(
|
||||
|
||||
// read resultset head
|
||||
template <class Stream>
|
||||
template <BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void boost::mysql::connection<Stream>::read_resultset_head(
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
@ -499,19 +570,20 @@ void boost::mysql::connection<Stream>::read_resultset_head(
|
||||
detail::clear_errors(err, diag);
|
||||
return detail::read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::read_resultset_head(execution_state& st)
|
||||
template <BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType>
|
||||
void boost::mysql::connection<Stream>::read_resultset_head(ExecutionStateType& st)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
@ -519,17 +591,19 @@ void boost::mysql::connection<Stream>::read_resultset_head(execution_state& st)
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
template <
|
||||
BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
|
||||
boost::mysql::connection<Stream>::async_read_resultset_head(
|
||||
execution_state& st,
|
||||
ExecutionStateType& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return detail::async_read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
@ -544,14 +618,24 @@ boost::mysql::rows_view boost::mysql::connection<Stream>::read_some_rows(
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
return detail::read_some_rows(get_channel(), st, err, diag);
|
||||
return detail::read_some_rows_dynamic(
|
||||
get_channel(),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
boost::mysql::rows_view boost::mysql::connection<Stream>::read_some_rows(execution_state& st)
|
||||
{
|
||||
detail::error_block blk;
|
||||
rows_view res = detail::read_some_rows(get_channel(), st, blk.err, blk.diag);
|
||||
rows_view res = detail::read_some_rows_dynamic(
|
||||
get_channel(),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
return res;
|
||||
}
|
||||
@ -566,9 +650,64 @@ boost::mysql::connection<Stream>::async_read_some_rows(
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return detail::async_read_some_rows(get_channel(), st, diag, std::forward<CompletionToken>(token));
|
||||
return detail::async_read_some_rows_dynamic(
|
||||
get_channel(),
|
||||
detail::impl_access::get_impl(st).get_interface(),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef BOOST_MYSQL_CXX14
|
||||
template <class Stream>
|
||||
template <class SpanRowType, class... RowType>
|
||||
std::size_t boost::mysql::connection<Stream>::read_some_rows(
|
||||
static_execution_state<RowType...>& st,
|
||||
span<SpanRowType> output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
return detail::read_some_rows_static(get_channel(), st, output, err, diag);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <class SpanRowType, class... RowType>
|
||||
std::size_t boost::mysql::connection<Stream>::read_some_rows(
|
||||
static_execution_state<RowType...>& st,
|
||||
span<SpanRowType> output
|
||||
)
|
||||
{
|
||||
detail::error_block blk;
|
||||
auto res = detail::read_some_rows_static(get_channel(), st, output, blk.err, blk.diag);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <
|
||||
class SpanRowType,
|
||||
class... RowType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, std::size_t))
|
||||
boost::mysql::connection<Stream>::async_read_some_rows(
|
||||
static_execution_state<RowType...>& st,
|
||||
span<SpanRowType> output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return detail::async_read_some_rows_static(
|
||||
get_channel(),
|
||||
st,
|
||||
output,
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ping
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::ping(error_code& err, diagnostics& diag)
|
||||
|
@ -14,7 +14,17 @@
|
||||
|
||||
struct boost::mysql::detail::diagnostics_access
|
||||
{
|
||||
static void assign(diagnostics& obj, string_view from) { obj.msg_.assign(from.begin(), from.end()); }
|
||||
static void assign_client(diagnostics& obj, std::string from)
|
||||
{
|
||||
obj.msg_ = std::move(from);
|
||||
obj.is_server_ = false;
|
||||
}
|
||||
|
||||
static void assign_server(diagnostics& obj, std::string from)
|
||||
{
|
||||
obj.msg_ = std::move(from);
|
||||
obj.is_server_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -45,6 +45,19 @@ inline const char* error_to_string(client_errc error) noexcept
|
||||
case boost::mysql::client_errc::server_doesnt_support_ssl:
|
||||
return "The connection is configured to require SSL, but the server doesn't allow SSL connections. "
|
||||
"Configure SSL on your server or change your connection to not require SSL";
|
||||
case boost::mysql::client_errc::metadata_check_failed:
|
||||
return "The static interface detected a type mismatch between your declared row type and what the "
|
||||
"server returned. Verify your type definitions.";
|
||||
case boost::mysql::client_errc::num_resultsets_mismatch:
|
||||
return "The static interface detected a mismatch between the number of resultsets passed as template "
|
||||
"arguments to static_results<T1, T2...>/static_execution_state<T1, T2...> and the number of "
|
||||
"results returned by server";
|
||||
case boost::mysql::client_errc::static_row_parsing_error:
|
||||
return "The static interface encountered an error when parsing a field into a C++ data structure.";
|
||||
case boost::mysql::client_errc::row_type_mismatch:
|
||||
return "The StaticRow type passed to read_some_rows does not correspond to the resultset type being "
|
||||
"read";
|
||||
|
||||
default: return "<unknown MySQL client error>";
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 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_EXECUTION_STATE_HPP
|
||||
#define BOOST_MYSQL_IMPL_EXECUTION_STATE_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
struct boost::mysql::detail::execution_state_access
|
||||
{
|
||||
static detail::execution_state_impl& get_impl(execution_state& st) noexcept { return st.impl_; }
|
||||
};
|
||||
|
||||
#endif
|
@ -47,6 +47,10 @@ inline column_type compute_field_type(
|
||||
std::uint16_t collation
|
||||
)
|
||||
{
|
||||
// Some protocol_field_types seem to not be sent by the server. We've found instances
|
||||
// where some servers, with certain SQL statements, send some of the "apparently not sent"
|
||||
// types (e.g. MariaDB was sending medium_blob only if you SELECT TEXT variables - but not with TEXT
|
||||
// columns). So we've taken a defensive approach here
|
||||
switch (protocol_type)
|
||||
{
|
||||
case protocol_field_type::decimal:
|
||||
@ -66,8 +70,14 @@ inline column_type compute_field_type(
|
||||
case protocol_field_type::time: return column_type::time;
|
||||
case protocol_field_type::year: return column_type::year;
|
||||
case protocol_field_type::json: return column_type::json;
|
||||
case protocol_field_type::enum_: return column_type::enum_; // in theory not set
|
||||
case protocol_field_type::set: return column_type::set; // in theory not set
|
||||
case protocol_field_type::string: return compute_field_type_string(flags, collation);
|
||||
case protocol_field_type::varchar: // in theory not sent
|
||||
case protocol_field_type::var_string: return compute_field_type_var_string(collation);
|
||||
case protocol_field_type::tiny_blob: // in theory not sent
|
||||
case protocol_field_type::medium_blob: // in theory not sent
|
||||
case protocol_field_type::long_blob: // in theory not sent
|
||||
case protocol_field_type::blob: return compute_field_type_blob(collation);
|
||||
default: return column_type::unknown;
|
||||
}
|
||||
@ -97,6 +107,9 @@ struct boost::mysql::detail::metadata_access
|
||||
{
|
||||
return metadata(msg, copy_strings);
|
||||
}
|
||||
|
||||
// This is used by the tests
|
||||
static void set_type(metadata& obj, column_type v) noexcept { obj.type_ = v; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019-2023 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_RESULTS_HPP
|
||||
#define BOOST_MYSQL_IMPL_RESULTS_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/results.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
struct boost::mysql::detail::results_access
|
||||
{
|
||||
static execution_state_impl& get_impl(results& result) noexcept { return result.impl_; }
|
||||
};
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user