mirror of
https://github.com/boostorg/mysql.git
synced 2025-05-11 13:44:35 +00:00
Multi-resultset
Added support for running stored procedures that SELECT data Added support for running multiple semicolon-separated queries Added support for running stored procedures with OUT params Added resultset and resultset_view Fixed documentation typos and wording Refactored object creation in tests Close #133 Close #132 Close #8
This commit is contained in:
parent
0f5a89b345
commit
ed007e31ae
4
.github/workflows/build-code.yml
vendored
4
.github/workflows/build-code.yml
vendored
@ -19,9 +19,11 @@ jobs:
|
||||
OPENSSL_ROOT: /usr/local/opt/openssl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: unlink /usr/local/bin/python
|
||||
- run: ln -s /usr/local/bin/python3 /usr/local/bin/python
|
||||
- run: bash tools/setup_db_osx.sh
|
||||
- run: |
|
||||
python3 tools/ci.py \
|
||||
python tools/ci.py \
|
||||
--build-kind=b2 \
|
||||
--source-dir=$(pwd) \
|
||||
--toolset=clang \
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,7 +3,7 @@ build/
|
||||
.project
|
||||
.settings/
|
||||
.pydevproject
|
||||
private/
|
||||
/private/
|
||||
build-*/
|
||||
CMakeSettings.json
|
||||
out/
|
||||
|
4
doc/images/protocol_multi_resultset.svg
Normal file
4
doc/images/protocol_multi_resultset.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 33 KiB |
@ -22,6 +22,7 @@
|
||||
|
||||
[template nochunk[] [block '''<?dbhtml stop-chunking?>''']]
|
||||
[template mdash[] '''— ''']
|
||||
[template link_to_file[path][^'''<ulink url="https://github.com/boostorg/mysql/blob/master/'''[path]'''">'''[path]'''</ulink>''']]
|
||||
[template include_file[path][^<'''<ulink url="https://github.com/boostorg/mysql/blob/master/include/'''[path]'''">'''[path]'''</ulink>'''>]]
|
||||
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
|
||||
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
|
||||
@ -91,18 +92,19 @@
|
||||
[include 03_overview.qbk]
|
||||
[include 04_queries.qbk]
|
||||
[include 05_prepared_statements.qbk]
|
||||
[include 06_multi_function.qbk]
|
||||
[include 07_fields.qbk]
|
||||
[include 08_metadata.qbk]
|
||||
[include 09_async.qbk]
|
||||
[include 10_ssl.qbk]
|
||||
[include 11_other_streams.qbk]
|
||||
[include 12_error_handling.qbk]
|
||||
[include 13_connparams.qbk]
|
||||
[include 14_reconnecting.qbk]
|
||||
[include 15_charsets.qbk]
|
||||
[include 16_examples.qbk]
|
||||
[include 17_tests.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]
|
||||
|
||||
|
||||
[section:ref Reference]
|
||||
|
@ -22,20 +22,29 @@ commit them using `COMMIT` and rolling them back using `ROLLBACK`.
|
||||
|
||||
[heading Limitations]
|
||||
|
||||
* You can only execute a single query at a time. Passing in several queries separated by semicolons
|
||||
will produce an error.
|
||||
* At the moment, there is no equivalent to
|
||||
[@https://dev.mysql.com/doc/c-api/8.0/en/mysql-real-escape-string.html `mysql_real_escape_string`]
|
||||
to sanitize user provided input. This limits text queries to queries without parameters.
|
||||
Doing composition by hand can lead to SQL injection vulnerabilities.
|
||||
Please use [link mysql.prepared_statements prepared statements]
|
||||
instead, which perform composition server-side in a safe way.
|
||||
At the moment, there is no equivalent to
|
||||
[@https://dev.mysql.com/doc/c-api/8.0/en/mysql-real-escape-string.html `mysql_real_escape_string`]
|
||||
to sanitize user provided input. This limits text queries to queries without parameters.
|
||||
Doing composition by hand can lead to SQL injection vulnerabilities.
|
||||
Please use [link mysql.prepared_statements prepared statements]
|
||||
instead, which perform composition server-side in a safe way.
|
||||
|
||||
[warning
|
||||
[*SQL injection warning]: if you compose queries by concatenating strings without sanitization,
|
||||
your code is vulnerable to SQL injection attacks. Use prepared statements when possible!
|
||||
]
|
||||
|
||||
[heading Running multiple queries at once]
|
||||
|
||||
You can run several semicolon-separated queries in a single call by enabling
|
||||
the [refmem handshake_params multi_queries] option. You can find an example
|
||||
[link mysql.multi_resultset.multi_queries here].
|
||||
|
||||
[warning
|
||||
[*SQL injection warning]: do not use this feature if any of your queries contains
|
||||
untrusted input. You can create SQL injection vulnerabilities if you do.
|
||||
]
|
||||
|
||||
[heading Use cases]
|
||||
|
||||
You should generally prefer prepared statements over text queries. Text queries can be useful for simple,
|
||||
|
104
doc/qbk/06_multi_resultset.qbk
Normal file
104
doc/qbk/06_multi_resultset.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:multi_resultset Multi-resultset: stored procedures and multi-queries]
|
||||
|
||||
[heading Using stored procedures]
|
||||
|
||||
[mysqllink create-procedure.html Stored procedures] can be called using the `CALL` SQL statement.
|
||||
You can use `CALL` statements from both text queries and prepared statements, in a similar way
|
||||
to other SQL statements. Contrary to other statements, `CALL` may generate more than one resultset.
|
||||
|
||||
For example, given a stored procedure like this:
|
||||
|
||||
[multi_resultset_procedure]
|
||||
|
||||
You can call it using a prepared statement, with the usual syntax:
|
||||
|
||||
[multi_resultset_call]
|
||||
|
||||
MySQL responds here with two resultsets:
|
||||
|
||||
* 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:
|
||||
|
||||
[multi_resultset_first_resultset]
|
||||
|
||||
[heading Determining the number of resultsets]
|
||||
|
||||
To know the number of resultsets to expect from a `CALL` statement, use these rules:
|
||||
|
||||
* For every statement that retrieves data (e.g. a `SELECT` statement), a resultset is sent.
|
||||
`SELECT ... INTO <variables>` statements don't cause a resultset to be sent.
|
||||
* Statements that don't retrieve columns (e.g. `UPDATE`, `DELETE`) don't cause a resultset to be sent.
|
||||
* An empty resultset containing information about the `CALL` statement execution is always sent last.
|
||||
|
||||
Some examples:
|
||||
|
||||
[!teletype]
|
||||
```
|
||||
-- Calling proc1 produces only 1 resultset because it only contains statements that
|
||||
-- don't retrieve data
|
||||
CREATE PROCEDURE proc1(IN pin_order_id INT, IN pin_quantity INT)
|
||||
BEGIN
|
||||
START TRANSACTION;
|
||||
UPDATE orders SET quantity = pin_quantity WHERE id = pin_order_id;
|
||||
INSERT INTO audit_log (msg) VALUES ("Updated order...");
|
||||
COMMIT;
|
||||
END
|
||||
```
|
||||
|
||||
[!teletype]
|
||||
```
|
||||
-- Calling proc2 produces 3 resultsets: one for the orders SELECT, one for the
|
||||
-- line_items SELECT, and one for the CALL statement
|
||||
CREATE PROCEDURE proc2(IN pin_order_id INT)
|
||||
BEGIN
|
||||
START TRANSACTION READ ONLY;
|
||||
SELECT * FROM orders WHERE id = pin_order_id;
|
||||
SELECT * FROM line_items WHERE order_id = pin_order_id;
|
||||
COMMIT;
|
||||
END
|
||||
```
|
||||
|
||||
[heading Output parameters]
|
||||
|
||||
You can get the value of `OUT` and `INOUT` parameters in stored procedures by using
|
||||
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:
|
||||
|
||||
[multi_resultset_out_params]
|
||||
|
||||
[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 query] call.
|
||||
For security, this capability is disabled by default. Enabling it requires setting [refmem handshake_params multi_queries]
|
||||
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.
|
||||
|
||||
[endsect]
|
@ -107,8 +107,49 @@ of useful information in this object, like the column name, its type, whether it
|
||||
|
||||
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,
|
||||
[*these functions have `st.complete() == true` as precondition].
|
||||
[*it can only be accessed once [refmem execution_state complete] returns `true`].
|
||||
|
||||
[heading Using multi-function operations with stored procedures and multi-queries]
|
||||
|
||||
When using operations that return more than one resultset (e.g. when calling stored procedures),
|
||||
the protocol is slightly more complex:
|
||||
|
||||
[$mysql/images/protocol_multi_resultset.svg [align center]]
|
||||
|
||||
The message exchange is as follows:
|
||||
|
||||
* A single execution request is sent to the server.
|
||||
* The server sends a first resultset, as in the single resultset case.
|
||||
The OK packet contains a flag indicating that another resultset 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:
|
||||
|
||||
[multi_function_stored_procedure]
|
||||
|
||||
Note that we're using [refmem execution_state should_read_rows] instead of [refmem 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.
|
||||
|
||||
[heading Multi-resultset in the general case]
|
||||
|
||||
`execution_state` can be seen as a state machine 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`.
|
||||
You should call functions like `start_query` or `start_statement_execution` to start the operation.
|
||||
* [refmem 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],
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
[link mysql.examples.source_script This example] shows this general case. It uses
|
||||
multi-queries to run an arbitrary SQL script file.
|
||||
|
||||
[heading:read_some_rows More on read_some_rows]
|
||||
|
@ -8,7 +8,7 @@
|
||||
[section:async Going async]
|
||||
[nochunk]
|
||||
|
||||
Following __Asio__'s convetion, all network operations have
|
||||
Following __Asio__'s convention, all network operations have
|
||||
asynchronous versions with the same name prefixed by `async_`.
|
||||
The last parameter to async operations is a __CompletionToken__,
|
||||
which dictates how the asynchronous operation will be managed
|
@ -20,11 +20,11 @@ password is provided to __Self__ in plain text,
|
||||
but it is not sent like that to the server (see below for more info).
|
||||
If your password is empty, just provide an empty string.
|
||||
|
||||
MySQL implements several ways of authentication with the server,
|
||||
MySQL implements several methods of authentication with the server,
|
||||
in what is called [mysqllink pluggable-authentication.html
|
||||
pluggable authentication]. The decission of which authentication
|
||||
plugin to use is made in a per-user basis. This information
|
||||
is stored in the `mysql.user` table. In addition to this,
|
||||
pluggable authentication]. The authentication
|
||||
plugin used is chosen on a per-user basis. This information
|
||||
is stored in the `mysql.user` table. Additionally,
|
||||
servers define a default authentication plugin
|
||||
(see [mysqllink server-system-variables.html#sysvar_authentication_policy `authentication_policy`] and
|
||||
[mysqllink server-system-variables.html#sysvar_default_authentication_plugin
|
@ -22,8 +22,10 @@ Here is a list of available examples:
|
||||
# [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 Settings timeouts]
|
||||
# [link mysql.examples.timeouts Setting timeouts]
|
||||
# [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]
|
||||
|
||||
@ -210,4 +212,41 @@ 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]
|
||||
|
||||
This example demonstrates how to source a .sql script using the
|
||||
[link mysql.multi_resultset.multi_queries multi-queries feature].
|
||||
|
||||
Note that commands like `DELIMITER` won't work, since these are handled
|
||||
by the `mysql` command line tool, rather than the server.
|
||||
|
||||
The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/source_script.cpp]
|
||||
[example_source_script]
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect] [/ examples]
|
@ -28,6 +28,8 @@
|
||||
<member><link linkend="mysql.ref.boost__mysql__handshake_params">handshake_params</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__metadata">metadata</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__results">results</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__resultset_view">resultset_view</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__resultset">resultset</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__row">row</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__row_view">row_view</link></member>
|
||||
<member><link linkend="mysql.ref.boost__mysql__rows">rows</link></member>
|
||||
|
@ -9,54 +9,6 @@
|
||||
# when we're the main project and BOOST_MYSQL_INTEGRATION_TESTS is on
|
||||
find_package(Boost ${BOOST_MYSQL_VERSION} REQUIRED COMPONENTS context)
|
||||
|
||||
# Compile the example
|
||||
function (build_example EXECUTABLE_NAME CPPFILE)
|
||||
add_executable(
|
||||
${EXECUTABLE_NAME}
|
||||
${CPPFILE}
|
||||
)
|
||||
target_link_libraries(
|
||||
${EXECUTABLE_NAME}
|
||||
PRIVATE
|
||||
Boost::disable_autolinking
|
||||
Boost::mysql
|
||||
Boost::context
|
||||
boost_mysql_asio
|
||||
)
|
||||
common_target_settings(${EXECUTABLE_NAME})
|
||||
endfunction()
|
||||
|
||||
# Run it as a test
|
||||
function (test_example EXECUTABLE_NAME CONNECTION_ARG)
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
COMMAND ${EXECUTABLE_NAME} example_user example_password ${CONNECTION_ARG}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# Run it as a test using Valgrind
|
||||
function (memcheck_example EXECUTABLE_NAME CONNECTION_ARG)
|
||||
set(TEST_NAME "${EXECUTABLE_NAME}_memcheck")
|
||||
add_memcheck_test(
|
||||
NAME ${TEST_NAME}
|
||||
TARGET ${EXECUTABLE_NAME}
|
||||
ARGUMENTS example_user example_password ${CONNECTION_ARG}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# Build and run it as either of them
|
||||
function (add_example EXAMPLE_NAME DO_MEMCHECK CONNECTION_ARG)
|
||||
set(EXECUTABLE_NAME "boost_mysql_example_${EXAMPLE_NAME}")
|
||||
|
||||
build_example(${EXECUTABLE_NAME} "${EXAMPLE_NAME}.cpp")
|
||||
|
||||
if (BOOST_MYSQL_VALGRIND_TESTS AND DO_MEMCHECK)
|
||||
memcheck_example(${EXECUTABLE_NAME} ${CONNECTION_ARG})
|
||||
else()
|
||||
test_example(${EXECUTABLE_NAME} ${CONNECTION_ARG})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Get the MySQL hostname to use for examples
|
||||
if(DEFINED ENV{BOOST_MYSQL_SERVER_HOST})
|
||||
set(SERVER_HOST $ENV{BOOST_MYSQL_SERVER_HOST})
|
||||
@ -64,20 +16,92 @@ else()
|
||||
set(SERVER_HOST "localhost")
|
||||
endif()
|
||||
|
||||
add_library(boost_mysql_examples_common INTERFACE)
|
||||
target_link_libraries(
|
||||
boost_mysql_examples_common
|
||||
INTERFACE
|
||||
Boost::disable_autolinking
|
||||
Boost::mysql
|
||||
boost_mysql_asio
|
||||
)
|
||||
|
||||
# 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})
|
||||
|
||||
if (BOOST_MYSQL_VALGRIND_TESTS)
|
||||
add_memcheck_test(
|
||||
NAME "${EXECUTABLE_NAME}_memcheck"
|
||||
TARGET ${EXECUTABLE_NAME}
|
||||
ARGUMENTS ${ARGN}
|
||||
)
|
||||
else()
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
COMMAND ${EXECUTABLE_NAME} ${ARGN}
|
||||
)
|
||||
endif()
|
||||
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_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})
|
||||
add_test(
|
||||
NAME ${EXECUTABLE_NAME}
|
||||
COMMAND
|
||||
python
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/private/run_stored_procedures.py
|
||||
$<TARGET_FILE:${EXECUTABLE_NAME}>
|
||||
${SERVER_HOST}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# Build and run all the examples
|
||||
add_example(snippets TRUE ${SERVER_HOST})
|
||||
add_example(tutorial TRUE ${SERVER_HOST})
|
||||
add_example(text_queries TRUE ${SERVER_HOST})
|
||||
add_example(prepared_statements TRUE ${SERVER_HOST})
|
||||
add_example(async_callbacks TRUE ${SERVER_HOST})
|
||||
add_example(async_coroutines FALSE ${SERVER_HOST})
|
||||
add_example(async_coroutinescpp20 TRUE ${SERVER_HOST})
|
||||
add_example(async_futures TRUE ${SERVER_HOST})
|
||||
add_example(default_completion_tokens TRUE ${SERVER_HOST})
|
||||
add_example(metadata TRUE ${SERVER_HOST})
|
||||
add_example(ssl TRUE ${SERVER_HOST})
|
||||
add_example(timeouts TRUE ${SERVER_HOST})
|
||||
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})
|
||||
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 TRUE "/var/run/mysqld/mysqld.sock")
|
||||
add_example(unix_socket example_user example_password)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
import os ;
|
||||
|
||||
path-constant this_dir : . ;
|
||||
|
||||
# The hostname to use for examples
|
||||
local hostname = [ os.environ BOOST_MYSQL_SERVER_HOST ] ;
|
||||
if $(hostname) = ""
|
||||
@ -15,7 +17,7 @@ if $(hostname) = ""
|
||||
}
|
||||
|
||||
# Example list
|
||||
local examples_no_unix =
|
||||
local regular_examples =
|
||||
snippets
|
||||
tutorial
|
||||
text_queries
|
||||
@ -30,10 +32,7 @@ local examples_no_unix =
|
||||
timeouts
|
||||
;
|
||||
|
||||
local example_targets ;
|
||||
|
||||
# Non-UNIX
|
||||
for local example in $(examples_no_unix)
|
||||
for local example in $(regular_examples)
|
||||
{
|
||||
local example_name = "boost_mysql_example_$(example)" ;
|
||||
unit-test $(example_name)
|
||||
@ -44,23 +43,36 @@ for local example in $(examples_no_unix)
|
||||
:
|
||||
<testing.arg>"example_user example_password $(hostname)"
|
||||
;
|
||||
explicit $(example_name) ;
|
||||
example_targets += $(example_name) ;
|
||||
}
|
||||
|
||||
# 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
|
||||
:
|
||||
unix_socket.cpp
|
||||
/boost/mysql/test//mysql
|
||||
:
|
||||
<testing.arg>"example_user example_password"
|
||||
;
|
||||
explicit boost_mysql_example_unix_socket ;
|
||||
example_targets += boost_mysql_example_unix_socket ;
|
||||
:
|
||||
unix_socket.cpp
|
||||
/boost/mysql/test//mysql
|
||||
:
|
||||
<testing.arg>"example_user example_password"
|
||||
;
|
||||
}
|
||||
|
||||
# Helper to run all of them in one go
|
||||
alias boost_mysql_all_examples : $(example_targets) ;
|
||||
explicit boost_mysql_all_examples ;
|
||||
# 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.cpp
|
||||
/boost/mysql/test//mysql
|
||||
: requirements
|
||||
<testing.arg>"example_user example_password $(hostname) $(this_dir)/private/test_script.sql"
|
||||
;
|
||||
|
@ -26,6 +26,11 @@ CREATE TABLE employee(
|
||||
company_id CHAR(10) NOT NULL,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id)
|
||||
);
|
||||
CREATE TABLE audit_log(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
t TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
msg TEXT
|
||||
);
|
||||
|
||||
INSERT INTO company (name, id) VALUES
|
||||
("Award Winning Company, Inc.", "AWC"),
|
||||
|
316
example/db_setup_stored_procedures.sql
Normal file
316
example/db_setup_stored_procedures.sql
Normal file
@ -0,0 +1,316 @@
|
||||
--
|
||||
-- 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)
|
||||
--
|
||||
|
||||
-- Connection system variables
|
||||
SET NAMES utf8;
|
||||
|
||||
-- Database
|
||||
DROP DATABASE IF EXISTS boost_mysql_stored_procedures;
|
||||
CREATE DATABASE boost_mysql_stored_procedures;
|
||||
USE boost_mysql_stored_procedures;
|
||||
|
||||
-- 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'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Table definitions
|
||||
CREATE TABLE products (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
short_name VARCHAR(100) NOT NULL,
|
||||
descr TEXT,
|
||||
price INT NOT NULL,
|
||||
FULLTEXT(short_name, descr)
|
||||
);
|
||||
|
||||
CREATE TABLE orders(
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`status` ENUM('draft', 'pending_payment', 'complete') NOT NULL DEFAULT 'draft'
|
||||
);
|
||||
|
||||
CREATE TABLE order_items(
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
);
|
||||
|
||||
-- Procedures
|
||||
DELIMITER //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_products(IN p_search VARCHAR(50))
|
||||
BEGIN
|
||||
DECLARE max_products INT DEFAULT 20;
|
||||
IF p_search IS NULL THEN
|
||||
SELECT id, short_name, descr, price
|
||||
FROM products
|
||||
LIMIT max_products;
|
||||
ELSE
|
||||
SELECT id, short_name, descr, price FROM products
|
||||
WHERE MATCH(short_name, descr) AGAINST(p_search)
|
||||
LIMIT max_products;
|
||||
END IF;
|
||||
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`;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_order(
|
||||
IN p_order_id INT
|
||||
)
|
||||
BEGIN
|
||||
DECLARE order_status TEXT;
|
||||
START TRANSACTION READ ONLY;
|
||||
|
||||
-- Check parameters
|
||||
IF p_order_id IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1048, MESSAGE_TEXT = 'get_order: invalid parameters';
|
||||
END IF;
|
||||
|
||||
-- Check that the order exists
|
||||
SELECT `status`
|
||||
INTO order_status
|
||||
FROM orders WHERE id = p_order_id;
|
||||
IF order_status IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given order does not exist';
|
||||
END IF;
|
||||
|
||||
-- Return the order
|
||||
SELECT
|
||||
p_order_id AS id,
|
||||
order_status AS `status`;
|
||||
SELECT
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = p_order_id;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE get_orders()
|
||||
BEGIN
|
||||
SELECT id, `status` FROM orders;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE add_line_item(
|
||||
IN p_order_id INT,
|
||||
IN p_product_id INT,
|
||||
IN p_quantity INT,
|
||||
OUT pout_line_item_id INT
|
||||
)
|
||||
BEGIN
|
||||
DECLARE product_price INT;
|
||||
DECLARE order_status TEXT;
|
||||
START TRANSACTION;
|
||||
|
||||
-- Check parameters
|
||||
IF p_order_id IS NULL OR p_product_id IS NULL OR p_quantity IS NULL OR p_quantity <= 0 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1048, MESSAGE_TEXT = 'add_line_item: invalid params';
|
||||
END IF;
|
||||
|
||||
-- Ensure that the product is valid
|
||||
SELECT price INTO product_price FROM products WHERE id = p_product_id;
|
||||
IF product_price IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given product does not exist';
|
||||
END IF;
|
||||
|
||||
-- Get the order
|
||||
SELECT `status` INTO order_status FROM orders WHERE id = p_order_id;
|
||||
IF order_status IS NULL THEN
|
||||
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';
|
||||
END IF;
|
||||
|
||||
-- Insert the new item
|
||||
INSERT INTO order_items (order_id, product_id, quantity) VALUES (p_order_id, p_product_id, p_quantity);
|
||||
|
||||
-- Return value
|
||||
SET pout_line_item_id = LAST_INSERT_ID();
|
||||
|
||||
-- Return the edited order
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = p_order_id;
|
||||
SELECT
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = p_order_id;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE remove_line_item(
|
||||
IN p_line_item_id INT
|
||||
)
|
||||
BEGIN
|
||||
DECLARE order_id INT;
|
||||
DECLARE order_status TEXT;
|
||||
START TRANSACTION;
|
||||
|
||||
-- Check parameters
|
||||
IF p_line_item_id IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1048, MESSAGE_TEXT = 'remove_line_item: invalid params';
|
||||
END IF;
|
||||
|
||||
-- Get the order
|
||||
SELECT orders.id, orders.`status`
|
||||
INTO order_id, order_status
|
||||
FROM orders
|
||||
JOIN order_items items ON (orders.id = items.order_id)
|
||||
WHERE items.id = p_line_item_id;
|
||||
|
||||
IF order_status IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given order item does not exist';
|
||||
END IF;
|
||||
IF order_status <> 'draft' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT = 'The given order is not editable';
|
||||
END IF;
|
||||
|
||||
-- Delete the line item
|
||||
DELETE FROM order_items
|
||||
WHERE id = p_line_item_id;
|
||||
|
||||
-- Return the edited order
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = order_id;
|
||||
SELECT
|
||||
item.id AS id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = order_id;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE checkout_order(
|
||||
IN p_order_id INT,
|
||||
OUT pout_order_total INT
|
||||
)
|
||||
BEGIN
|
||||
DECLARE order_status TEXT;
|
||||
START TRANSACTION;
|
||||
|
||||
-- Check parameters
|
||||
IF p_order_id IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1048, MESSAGE_TEXT = 'checkout_order: invalid params';
|
||||
END IF;
|
||||
|
||||
-- Get the order
|
||||
SELECT `status`
|
||||
INTO order_status
|
||||
FROM orders WHERE id = p_order_id;
|
||||
|
||||
IF order_status IS NULL THEN
|
||||
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 order is not in a state that can be checked out';
|
||||
END IF;
|
||||
|
||||
-- Update the order
|
||||
UPDATE orders SET `status` = 'pending_payment' WHERE id = p_order_id;
|
||||
|
||||
-- Retrieve the total price
|
||||
SELECT SUM(prod.price * item.quantity)
|
||||
INTO pout_order_total
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = p_order_id;
|
||||
|
||||
-- Return the edited order
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = p_order_id;
|
||||
SELECT
|
||||
item.id AS item_id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = p_order_id;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
|
||||
CREATE DEFINER = 'sp_user'@'%' PROCEDURE complete_order(
|
||||
IN p_order_id INT
|
||||
)
|
||||
BEGIN
|
||||
DECLARE order_status TEXT;
|
||||
START TRANSACTION;
|
||||
|
||||
-- Check parameters
|
||||
IF p_order_id IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1048, MESSAGE_TEXT = 'complete_order: invalid params';
|
||||
END IF;
|
||||
|
||||
-- Get the order
|
||||
SELECT `status`
|
||||
INTO order_status
|
||||
FROM orders WHERE id = p_order_id;
|
||||
|
||||
IF order_status IS NULL THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1329, MESSAGE_TEXT = 'The given order does not exist';
|
||||
END IF;
|
||||
IF order_status <> 'pending_payment' THEN
|
||||
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 1000, MESSAGE_TEXT = 'The given order is not in a state that can be completed';
|
||||
END IF;
|
||||
|
||||
-- Update the order
|
||||
UPDATE orders SET `status` = 'complete' WHERE id = p_order_id;
|
||||
|
||||
-- Return the edited order
|
||||
SELECT id, `status`
|
||||
FROM orders WHERE id = p_order_id;
|
||||
SELECT
|
||||
item.id AS item_id,
|
||||
item.quantity AS quantity,
|
||||
prod.price AS unit_price
|
||||
FROM order_items item
|
||||
JOIN products prod ON item.product_id = prod.id
|
||||
WHERE item.order_id = p_order_id;
|
||||
|
||||
COMMIT;
|
||||
END //
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
-- Contents for the products table
|
||||
INSERT INTO products (price, short_name, descr) VALUES
|
||||
(6400, 'A Feast for Odin', 'A Feast for Odin is a points-driven game, with plethora of pathways to victory, with a range of risk balanced against reward. A significant portion of this is your central hall, which has a whopping -86 points of squares and a major part of your game is attempting to cover these up with various tiles. Likewise, long halls and island colonies can also offer large rewards, but they will have penalties of their own.'),
|
||||
(1600, 'Railroad Ink', 'The critically acclaimed roll and write game where you draw routes on your board trying to connect the exits at its edges. The more you connect, the more points you make, but beware: each incomplete route will make you lose points!'),
|
||||
(4000, 'Catan', 'Catan is a board game for two to four players in which you compete to gather resources and build the biggest settlements on the fictional island of Catan. It takes approximately one hour to play.'),
|
||||
(2500, 'Not Alone', 'It is the 25th century. You are a member of an intergalactic expedition shipwrecked on a mysterious planet named Artemia. While waiting for the rescue ship, you begin to explore the planet but an alien entity picks up your scent and begins to hunt you. You are NOT ALONE! Will you survive the dangers of Artemia?'),
|
||||
(4500, 'Dice Hospital', "In Dice Hospital, a worker placement board game, players are tasked with running a local hospital. Each round you'll be admitting new patients, hiring specialists, building new departments, and treating as many incoming patients as you can.")
|
||||
;
|
60
example/private/run_stored_procedures.py
Normal file
60
example/private/run_stored_procedures.py
Normal file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
|
||||
from subprocess import run, PIPE
|
||||
import argparse
|
||||
import re
|
||||
|
||||
|
||||
def _parse_order_id(output: str) -> str:
|
||||
res = re.search(r'Order: id=([0-9]*)', output)
|
||||
assert res is not None
|
||||
return res.group(1)
|
||||
|
||||
|
||||
def _parse_line_item_id(output: str) -> str:
|
||||
res = re.search(r'Created line item: id=([0-9]*)', output)
|
||||
assert res is not None
|
||||
return res.group(1)
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self, exe: str, host: str) -> None:
|
||||
self._exe = exe
|
||||
self._host = host
|
||||
|
||||
def run(self, subcmd: str, *args: str) -> str:
|
||||
cmdline = [self._exe, 'sp_user', 'sp_password', self._host, subcmd, *args]
|
||||
print(' + ', cmdline)
|
||||
res = run(cmdline, check=True, stdout=PIPE)
|
||||
print(res.stdout.decode())
|
||||
return res.stdout.decode()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('executable')
|
||||
parser.add_argument('host')
|
||||
args = parser.parse_args()
|
||||
|
||||
runner = Runner(args.executable, args.host)
|
||||
|
||||
runner.run('get-products', 'feast')
|
||||
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'))
|
||||
runner.run('add-line-item', order_id, '2', '2')
|
||||
runner.run('add-line-item', order_id, '3', '1')
|
||||
runner.run('remove-line-item', line_item_id)
|
||||
runner.run('get-order', order_id)
|
||||
runner.run('checkout-order', order_id)
|
||||
runner.run('complete-order', order_id)
|
||||
runner.run('get-orders')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
17
example/private/test_script.sql
Normal file
17
example/private/test_script.sql
Normal file
@ -0,0 +1,17 @@
|
||||
--
|
||||
-- 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)
|
||||
--
|
||||
|
||||
USE boost_mysql_examples;
|
||||
|
||||
CREATE TEMPORARY TABLE products (
|
||||
id VARCHAR(50) PRIMARY KEY,
|
||||
description VARCHAR(256)
|
||||
);
|
||||
|
||||
INSERT INTO products VALUES ('PTT', 'Potatoes'), ('CAR', NULL);
|
||||
SELECT * FROM products;
|
||||
DROP TABLE products;
|
@ -18,6 +18,8 @@
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/resultset.hpp>
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
#include <boost/mysql/row.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
@ -34,6 +36,7 @@
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/this_coro.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
|
||||
#include <iostream>
|
||||
@ -73,6 +76,8 @@ using boost::mysql::tcp_ssl_connection;
|
||||
}
|
||||
|
||||
const char* get_value_from_user() { return ""; }
|
||||
std::int64_t get_employee_id() { return 42; }
|
||||
std::string get_company_id() { return "HGS"; }
|
||||
|
||||
//[prepared_statements_execute
|
||||
// description, price and show_in_store are not trusted, since they may
|
||||
@ -448,6 +453,8 @@ void main_impl(int argc, char** argv)
|
||||
run_overview_coro(conn);
|
||||
}
|
||||
#endif
|
||||
|
||||
// prepared statements
|
||||
{
|
||||
//[prepared_statements_prepare
|
||||
// Table setup
|
||||
@ -477,6 +484,146 @@ void main_impl(int argc, char** argv)
|
||||
#endif
|
||||
conn.query("DROP TABLE products", result);
|
||||
}
|
||||
|
||||
// multi-resultset
|
||||
{
|
||||
results result;
|
||||
conn.query("DROP PROCEDURE IF EXISTS get_employee", result);
|
||||
|
||||
//[multi_resultset_procedure
|
||||
conn.query(
|
||||
R"(
|
||||
CREATE PROCEDURE get_employee(IN pin_employee_id INT)
|
||||
BEGIN
|
||||
SELECT * FROM employee WHERE id = pin_employee_id;
|
||||
END
|
||||
)",
|
||||
result
|
||||
);
|
||||
//]
|
||||
|
||||
//[multi_resultset_call
|
||||
// The procedure parameter, employe_id, will likely be obtained from an untrusted source,
|
||||
// so we will use a prepared statement
|
||||
statement get_employee_stmt = conn.prepare_statement("CALL get_employee(?)");
|
||||
|
||||
// Obtain the parameters required to call the statement, e.g. from a file or HTTP message
|
||||
std::int64_t employee_id = get_employee_id();
|
||||
|
||||
// Call the statement
|
||||
conn.execute_statement(get_employee_stmt, std::make_tuple(employee_id), result);
|
||||
//]
|
||||
|
||||
//[multi_resultset_first_resultset
|
||||
rows_view matched_employees = result.at(0).rows();
|
||||
// Use matched_employees as required
|
||||
//]
|
||||
|
||||
boost::ignore_unused(matched_employees);
|
||||
}
|
||||
{
|
||||
results result;
|
||||
conn.query("DROP PROCEDURE IF EXISTS create_employee", result);
|
||||
|
||||
//[multi_resultset_out_params
|
||||
// Setup the stored procedure
|
||||
conn.query(
|
||||
R"(
|
||||
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
|
||||
)",
|
||||
result
|
||||
);
|
||||
|
||||
// To retrieve output parameters, you must use prepared statements. Text queries don't support this
|
||||
// We specify placeholders for both IN and OUT parameters
|
||||
statement stmt = conn.prepare_statement("CALL create_employee(?, ?, ?, ?)");
|
||||
|
||||
// When executing the statement, we provide an actual value for the IN parameters,
|
||||
// and a dummy value for the OUT parameter. This value will be ignored, but it's required by the
|
||||
// protocol
|
||||
conn.execute_statement(stmt, std::make_tuple("HGS", "John", "Doe", nullptr), result);
|
||||
|
||||
// Retrieve output parameters. This row_view has an element per
|
||||
// OUT or INOUT parameter that used a ? placeholder
|
||||
row_view output_params = result.out_params();
|
||||
std::int64_t new_employee_id = output_params.at(0).as_int64();
|
||||
//]
|
||||
|
||||
boost::ignore_unused(new_employee_id);
|
||||
}
|
||||
{
|
||||
boost::mysql::tcp_ssl_connection conn(ctx.get_executor(), ssl_ctx);
|
||||
auto endpoint = *resolver.resolve(argv[3], boost::mysql::default_port_string).begin();
|
||||
|
||||
//[multi_resultset_multi_queries
|
||||
// The username and password to use
|
||||
boost::mysql::handshake_params params(
|
||||
argv[1], // username
|
||||
argv[2], // password
|
||||
"boost_mysql_examples" // database
|
||||
);
|
||||
|
||||
// Allows running multiple semicolon-separated in a single call.
|
||||
// We must set this before calling connect
|
||||
params.set_multi_queries(true);
|
||||
|
||||
// Connect to the server specifying that we want support for multi-queries
|
||||
conn.connect(endpoint, params);
|
||||
|
||||
// We can now use the multi-query feature.
|
||||
// This will result in three resultsets, one per query.
|
||||
results result;
|
||||
conn.query(
|
||||
R"(
|
||||
CREATE TEMPORARY TABLE posts (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
title VARCHAR (256),
|
||||
body TEXT
|
||||
);
|
||||
INSERT INTO posts (title, body) VALUES ('Breaking news', 'Something happened!');
|
||||
SELECT COUNT(*) FROM posts;
|
||||
)",
|
||||
result
|
||||
);
|
||||
//]
|
||||
|
||||
//[multi_resultset_results_as_collection
|
||||
// result is actually a random-access collection of resultsets.
|
||||
// The INSERT is the 2nd query, so we can access its resultset like this:
|
||||
boost::mysql::resultset_view insert_result = result.at(1);
|
||||
|
||||
// A resultset has metadata, rows, and additional data, like the last insert ID:
|
||||
std::int64_t post_id = insert_result.last_insert_id();
|
||||
|
||||
// The SELECT result is the third one, so we can access it like this:
|
||||
boost::mysql::resultset_view select_result = result.at(2);
|
||||
|
||||
// select_result is a view that points into result.
|
||||
// We can take ownership of it using the resultse class:
|
||||
boost::mysql::resultset owning_select_result(select_result); // valid even after result is destroyed
|
||||
|
||||
// We can access rows of resultset objects as usual:
|
||||
std::int64_t num_posts = owning_select_result.rows().at(0).at(0).as_int64();
|
||||
//]
|
||||
|
||||
boost::ignore_unused(post_id);
|
||||
boost::ignore_unused(num_posts);
|
||||
}
|
||||
|
||||
// multi-function
|
||||
{
|
||||
//[multi_function_setup
|
||||
results result;
|
||||
@ -541,6 +688,62 @@ void main_impl(int argc, char** argv)
|
||||
read_all_rows(st); // don't compromise further operations
|
||||
conn.query("DROP TABLE posts", result);
|
||||
}
|
||||
|
||||
{
|
||||
results result;
|
||||
conn.query("DROP PROCEDURE IF EXISTS get_company", result);
|
||||
|
||||
//[multi_function_stored_procedure
|
||||
// Setup the stored procedure
|
||||
conn.query(
|
||||
R"(
|
||||
CREATE PROCEDURE get_company(IN pin_company_id CHAR(10))
|
||||
BEGIN
|
||||
START TRANSACTION READ ONLY;
|
||||
SELECT * FROM company WHERE id = pin_company_id;
|
||||
SELECT * FROM employee WHERE company_id = pin_company_id;
|
||||
COMMIT;
|
||||
END
|
||||
)",
|
||||
result
|
||||
);
|
||||
|
||||
// Get the company ID to retrieve, possibly from the user
|
||||
std::string company_id = get_company_id();
|
||||
|
||||
// Call the procedure
|
||||
execution_state st;
|
||||
statement stmt = conn.prepare_statement("CALL get_company(?)");
|
||||
conn.start_statement_execution(stmt, std::make_tuple(company_id), st);
|
||||
|
||||
// The above code will generate 3 resultsets
|
||||
// Read the 1st one, which contains the matched companies
|
||||
while (st.should_read_rows())
|
||||
{
|
||||
rows_view company_batch = conn.read_some_rows(st);
|
||||
// Use the retrieved companies as required
|
||||
//<-
|
||||
boost::ignore_unused(company_batch);
|
||||
//->
|
||||
}
|
||||
|
||||
// Move on to the 2nd one, containing the employees for these companies
|
||||
conn.read_resultset_head(st);
|
||||
while (st.should_read_rows())
|
||||
{
|
||||
rows_view employee_batch = conn.read_some_rows(st);
|
||||
// Use the retrieved employees as required
|
||||
//<-
|
||||
boost::ignore_unused(employee_batch);
|
||||
//->
|
||||
}
|
||||
|
||||
// The last one is an empty resultset containing information about the
|
||||
// CALL statement itself. We're not interested in this
|
||||
conn.read_resultset_head(st);
|
||||
assert(st.complete());
|
||||
//]
|
||||
}
|
||||
}
|
||||
|
||||
// fields
|
||||
|
185
example/source_script.cpp
Normal file
185
example/source_script.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
//
|
||||
// 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_source_script
|
||||
|
||||
#include <boost/mysql.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* This example runs all command in a .sql file, using multi-queries.
|
||||
* Note that special commands that are handled by the mysql command line tool
|
||||
* (like DELIMITER) won't work.
|
||||
*
|
||||
* For this example, we will be using the 'boost_mysql_examples' database.
|
||||
* You can get this database by running db_setup.sql.
|
||||
* This example assumes you are connecting to a localhost MySQL server.
|
||||
*
|
||||
* This example uses synchronous functions and handles errors using exceptions.
|
||||
*/
|
||||
|
||||
// Reads a file into memory
|
||||
std::string read_file(const char* file_name)
|
||||
{
|
||||
std::ifstream ifs(file_name);
|
||||
if (!ifs)
|
||||
throw std::runtime_error("Cannot open file: " + std::string(file_name));
|
||||
return std::string(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void print_column_names(boost::mysql::metadata_collection_view meta_collection)
|
||||
{
|
||||
if (meta_collection.empty())
|
||||
return;
|
||||
|
||||
bool is_first = true;
|
||||
for (auto meta : meta_collection)
|
||||
{
|
||||
if (!is_first)
|
||||
{
|
||||
std::cout << " | ";
|
||||
}
|
||||
is_first = false;
|
||||
std::cout << meta.column_name();
|
||||
}
|
||||
std::cout << "\n-----------------\n";
|
||||
}
|
||||
|
||||
void print_row(boost::mysql::row_view row)
|
||||
{
|
||||
bool is_first = true;
|
||||
for (auto field : row)
|
||||
{
|
||||
if (!is_first)
|
||||
{
|
||||
std::cout << " | ";
|
||||
}
|
||||
is_first = false;
|
||||
std::cout << field;
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
void print_ok(const boost::mysql::execution_state& st)
|
||||
{
|
||||
std::cout << "Affected rows: " << st.affected_rows()
|
||||
<< "\n"
|
||||
"Last insert ID: "
|
||||
<< st.last_insert_id()
|
||||
<< "\n"
|
||||
"Warnings: "
|
||||
<< st.warning_count() << "\n\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname> <path-to-script>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Read the script file into memory
|
||||
std::string script_contents = read_file(argv[4]);
|
||||
|
||||
// Set up the io_context, SSL context and connection required to
|
||||
// connect to the server.
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
|
||||
boost::mysql::tcp_ssl_connection conn(ctx.get_executor(), ssl_ctx);
|
||||
|
||||
// Resolve the server hostname to get a collection of endpoints
|
||||
boost::asio::ip::tcp::resolver resolver(ctx.get_executor());
|
||||
auto endpoints = resolver.resolve(argv[3], boost::mysql::default_port_string);
|
||||
|
||||
// The username, password and database to use
|
||||
boost::mysql::handshake_params params(
|
||||
argv[1], // username
|
||||
argv[2], // password
|
||||
"boost_mysql_examples" // database
|
||||
);
|
||||
|
||||
// We're going to use multi-queries, which enables passing the server
|
||||
// a set of semicolon-separated queries. We need to explicitly enable support for it.
|
||||
params.set_multi_queries(true);
|
||||
|
||||
// We'll be using metadata strings to print column names, so we need to enable support for it
|
||||
conn.set_meta_mode(boost::mysql::metadata_mode::full);
|
||||
|
||||
// Connect to the server using the first endpoint returned by the resolver
|
||||
conn.connect(*endpoints.begin(), params);
|
||||
|
||||
// The executed commands may generate a lot of output, so we're going to
|
||||
// use multi-function operations (i.e. start_query) to read it in batches.
|
||||
boost::mysql::execution_state st;
|
||||
conn.start_query(script_contents, st);
|
||||
|
||||
// The main read loop. Each executed command will yield a resultset.
|
||||
// st.comoplete() returns true once all resultsets have been read.
|
||||
for (std::size_t resultset_number = 0; !st.complete(); ++resultset_number)
|
||||
{
|
||||
// Advance to next resultset, if required
|
||||
if (st.should_read_head())
|
||||
{
|
||||
conn.read_resultset_head(st);
|
||||
}
|
||||
|
||||
// Print the name of the fields
|
||||
std::cout << "Resultset number " << resultset_number << "\n";
|
||||
print_column_names(st.meta());
|
||||
|
||||
// Read the rows and print them
|
||||
while (st.should_read_rows())
|
||||
{
|
||||
boost::mysql::rows_view batch = conn.read_some_rows(st);
|
||||
for (auto row : batch)
|
||||
{
|
||||
print_row(row);
|
||||
}
|
||||
}
|
||||
|
||||
// Print OK packet data
|
||||
print_ok(st);
|
||||
}
|
||||
|
||||
// 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() << '\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;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
480
example/stored_procedures.cpp
Normal file
480
example/stored_procedures.cpp
Normal file
@ -0,0 +1,480 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql.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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
using boost::mysql::resultset_view;
|
||||
using boost::mysql::row_view;
|
||||
using boost::mysql::rows_view;
|
||||
using boost::mysql::string_view;
|
||||
|
||||
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.
|
||||
*/
|
||||
struct visitor
|
||||
{
|
||||
boost::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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
std::cout << " Line item: id=" << item.at(0) << ", quantity=" << item.at(1)
|
||||
<< ", unit_price=" << item.at(2).as_int64() / 100.0 << "$\n";
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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();
|
||||
if (line_items.empty())
|
||||
{
|
||||
std::cout << "No line items\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (row_view item : line_items)
|
||||
{
|
||||
print_line_item(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get-products <search-term>: full text search of the products table
|
||||
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(?)");
|
||||
|
||||
boost::mysql::results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(args.search), result);
|
||||
auto products = result.front();
|
||||
std::cout << "Your search returned the following products:\n";
|
||||
for (auto product : products.rows())
|
||||
{
|
||||
std::cout << "* ID: " << product.at(0) << '\n'
|
||||
<< " Short name: " << product.at(1) << '\n'
|
||||
<< " Description: " << product.at(2) << '\n'
|
||||
<< " Price: " << product.at(3).as_int64() / 100.0 << "$" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// create-order: creates a new order
|
||||
void operator()(const create_order_args&) const
|
||||
{
|
||||
// Since create_order doesn't have user-supplied params, we can use query()
|
||||
boost::mysql::results result;
|
||||
conn.query("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.at(0).rows().at(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(?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(args.order_id), result);
|
||||
|
||||
// Print the result to stdout. get_order() returns a resultset for
|
||||
// the retrieved order and another for the line items. If the order can't
|
||||
// be found, get_order() raises an error using SIGNAL, which will make
|
||||
// execute_statement() fail with an exception.
|
||||
std::cout << "Retrieved order\n";
|
||||
print_order_with_items(result.at(0), result.at(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 query()
|
||||
boost::mysql::results result;
|
||||
conn.query("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();
|
||||
if (orders.empty())
|
||||
{
|
||||
std::cout << "No orders found" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (row_view order : result.front().rows())
|
||||
{
|
||||
print_order(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(?, ?, ?, ?)");
|
||||
|
||||
// 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;
|
||||
conn.execute_statement(
|
||||
stmt,
|
||||
std::make_tuple(args.order_id, args.product_id, args.quantity, nullptr),
|
||||
result
|
||||
);
|
||||
|
||||
// We can use results::out_params() to access the extra resultset containing
|
||||
// the OUT parameter
|
||||
auto new_line_item_id = result.out_params().at(0).as_int64();
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Created line item: id=" << new_line_item_id << "\n";
|
||||
print_order_with_items(result.at(0), result.at(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(?)");
|
||||
|
||||
// Run the procedure
|
||||
boost::mysql::results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(args.line_item_id), result);
|
||||
|
||||
// Print results to stdout
|
||||
std::cout << "Removed line item from order\n";
|
||||
print_order_with_items(result.at(0), result.at(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(?, ?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(args.order_id, nullptr), result);
|
||||
|
||||
// We can use results::out_params() to access the extra resultset containing
|
||||
// the OUT parameter
|
||||
auto total_amount = result.out_params().at(0).as_int64();
|
||||
|
||||
// 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.at(0), result.at(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(?)");
|
||||
|
||||
// Execute the statement
|
||||
boost::mysql::results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(args.order_id), result);
|
||||
|
||||
// Print the results to stdout
|
||||
std::cout << "Completed order\n";
|
||||
print_order_with_items(result.at(0), result.at(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);
|
||||
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(
|
||||
args.username, // username
|
||||
args.password, // password
|
||||
"boost_mysql_stored_procedures" // database to use
|
||||
);
|
||||
|
||||
// Hostname resolution
|
||||
auto endpoints = resolver.resolve(args.host, boost::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 boost::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;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
@ -364,13 +364,13 @@ public:
|
||||
* \brief Starts a text query as a multi-function operation.
|
||||
* \details
|
||||
* Writes the query request and reads the initial server response and the column
|
||||
* metadata, but not the generated rows, if any. After this operation completes, `st` will have
|
||||
* \ref execution_state::meta populated, and may become \ref execution_state::complete
|
||||
* if the operation did not generate any rows (e.g. it was an `UPDATE`).
|
||||
* metadata, but not the generated rows or subsequent resultsets, if any.
|
||||
* After this operation completes, `st` will have
|
||||
* \ref execution_state::meta populated.
|
||||
* Metadata will be populated according to `this->meta_mode()`.
|
||||
* \n
|
||||
* If the operation generated any rows, these <b>must</b> be read (by using
|
||||
* \ref read_some_rows) before engaging in any further network operation.
|
||||
* If the operation generated any rows or more than one resultset, these <b>must</b> be read (by using
|
||||
* \ref read_some_rows and \ref read_resultset_head) before engaging in any further network operation.
|
||||
* Otherwise, the results are undefined.
|
||||
* \n
|
||||
* `query_string` should be encoded using the connection's character set.
|
||||
@ -545,14 +545,13 @@ public:
|
||||
* \brief Starts a statement execution as a multi-function operation.
|
||||
* \details
|
||||
* Writes the execute request and reads the initial server response and the column
|
||||
* metadata, but not the generated rows, if any. After this operation completes, `st` will have
|
||||
* \ref execution_state::meta populated, and may become \ref execution_state::complete
|
||||
* if the operation did not generate any rows (e.g. it was an `UPDATE`).
|
||||
* Metadata will be populated according to `this->meta_mode()`.
|
||||
* metadata, but not the generated rows or subsequent resultsets, if any. After this operation completes,
|
||||
* `st` will have \ref execution_state::meta populated. Metadata will be populated according to
|
||||
* `this->meta_mode()`.
|
||||
* \n
|
||||
* If the operation generated any rows, these <b>must</b> be read (by using
|
||||
* \ref read_some_rows) before engaging in any further
|
||||
* operation involving server communication. Otherwise, the results are undefined.
|
||||
* If the operation generated any rows or more than one resultset, these <b>must</b> be read (by using
|
||||
* \ref read_some_rows and \ref read_resultset_head) before engaging in any further network operation.
|
||||
* Otherwise, the results are undefined.
|
||||
* \n
|
||||
* The statement actual parameters (`params`) are passed as a `std::tuple` of elements.
|
||||
* String parameters should be encoded using the connection's character set.
|
||||
@ -630,13 +629,12 @@ public:
|
||||
* \brief Starts a statement execution as a multi-function operation.
|
||||
* \details
|
||||
* Writes the execute request and reads the initial server response and the column
|
||||
* metadata, but not the generated rows, if any. After this operation completes, `st` will have
|
||||
* \ref execution_state::meta populated, and may become \ref execution_state::complete
|
||||
* if the operation did not generate any rows (e.g. it was an `UPDATE`).
|
||||
* metadata, but not the generated rows or any subsequent resultsets, if any. After this operation
|
||||
* completes, `st` will have \ref execution_state::meta populated.
|
||||
* \n
|
||||
* If the operation generated any rows, these <b>must</b> be read (by using
|
||||
* \ref connection::read_some_rows) before engaging in any further
|
||||
* operation involving server communication. Otherwise, the results are undefined.
|
||||
* If the operation generated any rows or more than one resultset, these <b>must</b> be read (by using
|
||||
* \ref read_some_rows and \ref read_resultset_head) before engaging in any further network operation.
|
||||
* Otherwise, the results are undefined.
|
||||
* \n
|
||||
* The statement actual parameters are passed as an iterator range.
|
||||
* String parameters should be encoded using the connection's character set.
|
||||
@ -757,9 +755,9 @@ public:
|
||||
/**
|
||||
* \brief Reads a batch of rows.
|
||||
* \details
|
||||
* The number of rows that will be read is unspecified. If the resultset being read
|
||||
* has still rows to read, at least one will be read. If there are no more
|
||||
* rows to be read, returns an empty `rows_view`.
|
||||
* The number of rows that will be read is unspecified. If the operation represented by `st`
|
||||
* has still rows to read, at least one will be read. If there are no more rows, or
|
||||
* `st.should_read_rows() == false`, returns an empty `rows_view`.
|
||||
* \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
|
||||
@ -802,6 +800,51 @@ public:
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
|
||||
/**
|
||||
* \brief Reads metadata for subsequent resultsets in a multi-resultset operation.
|
||||
* \details
|
||||
* If `st.should_read_head() == true`, this function will read the next resultset's
|
||||
* initial response message and metadata, if any. If the resultset indicates a failure
|
||||
* (e.g. the query associated to this resultset contained an error), this function will fail
|
||||
* with that error.
|
||||
* \n
|
||||
* If `st.should_read_head() == false`, this function is a no-op.
|
||||
* \n
|
||||
* This function is only relevant when using multi-function operations with statements
|
||||
* that return more than one resultset.
|
||||
*/
|
||||
void read_resultset_head(execution_state& st, error_code& err, diagnostics& info);
|
||||
|
||||
/// \copydoc read_resultset_head
|
||||
void read_resultset_head(execution_state& st);
|
||||
|
||||
/**
|
||||
* \copydoc read_resultset_head
|
||||
* \par Handler signature
|
||||
* 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)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_resultset_head(
|
||||
execution_state& st,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
)
|
||||
{
|
||||
return async_read_resultset_head(st, shared_diag(), std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/// \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)>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_resultset_head(
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
|
||||
);
|
||||
|
||||
/**
|
||||
* \brief Checks whether the server is alive.
|
||||
* \details
|
||||
|
@ -20,9 +20,9 @@ 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 rows_access;
|
||||
struct statement_access;
|
||||
|
||||
} // namespace detail
|
||||
|
@ -1,120 +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_IMPL_ROW_BASE_IPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_IMPL_ROW_BASE_IPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/row_base.hpp>
|
||||
|
||||
#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 std::size_t get_string_size(field_view f) noexcept
|
||||
{
|
||||
switch (f.kind())
|
||||
{
|
||||
case field_kind::string: return f.get_string().size();
|
||||
case field_kind::blob: return f.get_blob().size();
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned char* copy_string(unsigned char* buffer_it, field_view& f) noexcept
|
||||
{
|
||||
auto str = f.get_string();
|
||||
if (!str.empty())
|
||||
{
|
||||
assert(!overlaps(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();
|
||||
}
|
||||
return buffer_it;
|
||||
}
|
||||
|
||||
inline unsigned char* copy_blob(unsigned char* buffer_it, field_view& f) noexcept
|
||||
{
|
||||
auto b = f.get_blob();
|
||||
if (!b.empty())
|
||||
{
|
||||
assert(!overlaps(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();
|
||||
}
|
||||
return buffer_it;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
boost::mysql::detail::row_base::row_base(const field_view* fields, std::size_t size)
|
||||
: fields_(fields, fields + size)
|
||||
{
|
||||
copy_strings();
|
||||
}
|
||||
|
||||
boost::mysql::detail::row_base::row_base(const row_base& rhs) : fields_(rhs.fields_) { copy_strings(); }
|
||||
|
||||
boost::mysql::detail::row_base& boost::mysql::detail::row_base::operator=(const row_base& rhs)
|
||||
{
|
||||
if (this != &rhs)
|
||||
{
|
||||
fields_ = rhs.fields_;
|
||||
copy_strings();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void boost::mysql::detail::row_base::assign(const field_view* fields, std::size_t size)
|
||||
{
|
||||
fields_.assign(fields, fields + size);
|
||||
copy_strings();
|
||||
}
|
||||
|
||||
inline void boost::mysql::detail::row_base::copy_strings()
|
||||
{
|
||||
// Calculate the required size for the new strings
|
||||
std::size_t size = 0;
|
||||
for (auto f : fields_)
|
||||
{
|
||||
size += get_string_size(f);
|
||||
}
|
||||
|
||||
// Make space
|
||||
string_buffer_.resize(size);
|
||||
|
||||
// Copy strings and blobs
|
||||
unsigned char* buffer_it = string_buffer_.data();
|
||||
for (auto& f : fields_)
|
||||
{
|
||||
switch (f.kind())
|
||||
{
|
||||
case field_kind::string: buffer_it = copy_string(buffer_it, f); break;
|
||||
case field_kind::blob: buffer_it = copy_blob(buffer_it, f); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
assert(buffer_it == string_buffer_.data() + string_buffer_.size());
|
||||
}
|
||||
|
||||
#endif
|
197
include/boost/mysql/detail/auxiliar/impl/row_impl.ipp
Normal file
197
include/boost/mysql/detail/auxiliar/impl/row_impl.ipp
Normal file
@ -0,0 +1,197 @@
|
||||
//
|
||||
// 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_IMPL_ROW_IMPL_IPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_IMPL_ROW_IMPL_IPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#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())
|
||||
{
|
||||
case field_kind::string: return f.get_string().size();
|
||||
case field_kind::blob: return f.get_blob().size();
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned char* copy_string(unsigned char* buffer_it, field_view& f) noexcept
|
||||
{
|
||||
auto str = f.get_string();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_it, str.data(), str.size());
|
||||
f = field_view(string_view(reinterpret_cast<const char*>(buffer_it), str.size()));
|
||||
buffer_it += str.size();
|
||||
}
|
||||
return buffer_it;
|
||||
}
|
||||
|
||||
inline unsigned char* copy_blob(unsigned char* buffer_it, field_view& f) noexcept
|
||||
{
|
||||
auto b = f.get_blob();
|
||||
if (!b.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_it, b.data(), b.size());
|
||||
f = field_view(blob_view(buffer_it, b.size()));
|
||||
buffer_it += b.size();
|
||||
}
|
||||
return buffer_it;
|
||||
}
|
||||
|
||||
inline std::size_t copy_string_as_offset(
|
||||
unsigned char* buffer_first,
|
||||
std::size_t offset,
|
||||
field_view& f
|
||||
) noexcept
|
||||
{
|
||||
auto str = f.get_string();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_first + offset, str.data(), str.size());
|
||||
f = field_view_access::construct(string_view_offset(offset, str.size()), false);
|
||||
return str.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline std::size_t copy_blob_as_offset(
|
||||
unsigned char* buffer_first,
|
||||
std::size_t offset,
|
||||
field_view& f
|
||||
) noexcept
|
||||
{
|
||||
auto str = f.get_blob();
|
||||
if (!str.empty())
|
||||
{
|
||||
guarded_memcpy(buffer_first + offset, str.data(), str.size());
|
||||
f = field_view_access::construct(string_view_offset(offset, str.size()), true);
|
||||
return str.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
boost::mysql::detail::row_impl::row_impl(const field_view* fields, std::size_t size)
|
||||
: fields_(fields, fields + size)
|
||||
{
|
||||
copy_strings();
|
||||
}
|
||||
|
||||
boost::mysql::detail::row_impl::row_impl(const row_impl& rhs) : fields_(rhs.fields_) { copy_strings(); }
|
||||
|
||||
boost::mysql::detail::row_impl& boost::mysql::detail::row_impl::operator=(const row_impl& rhs)
|
||||
{
|
||||
assign(rhs.fields_.data(), rhs.fields_.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void boost::mysql::detail::row_impl::assign(const field_view* fields, std::size_t size)
|
||||
{
|
||||
// Protect against self-assignment. This is valid as long as we
|
||||
// don't implement sub-range operators (e.g. row_view[2:4])
|
||||
if (fields_.data() == fields)
|
||||
{
|
||||
assert(fields_.size() == size);
|
||||
}
|
||||
else
|
||||
{
|
||||
fields_.assign(fields, fields + size);
|
||||
string_buffer_.clear();
|
||||
copy_strings();
|
||||
}
|
||||
}
|
||||
|
||||
inline void boost::mysql::detail::row_impl::copy_strings()
|
||||
{
|
||||
// Calculate the required size for the new strings
|
||||
std::size_t size = 0;
|
||||
for (auto f : fields_)
|
||||
{
|
||||
size += get_string_size(f);
|
||||
}
|
||||
|
||||
// Make space. The previous fields should be in offset form
|
||||
string_buffer_.resize(string_buffer_.size() + size);
|
||||
|
||||
// Copy strings and blobs
|
||||
unsigned char* buffer_it = string_buffer_.data();
|
||||
for (auto& f : fields_)
|
||||
{
|
||||
switch (f.kind())
|
||||
{
|
||||
case field_kind::string: buffer_it = copy_string(buffer_it, f); break;
|
||||
case field_kind::blob: buffer_it = copy_blob(buffer_it, f); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
assert(buffer_it == string_buffer_.data() + size);
|
||||
}
|
||||
|
||||
inline void boost::mysql::detail::row_impl::copy_strings_as_offsets(std::size_t first, std::size_t num_fields)
|
||||
{
|
||||
// Preconditions
|
||||
assert(first <= fields_.size());
|
||||
assert(first + num_fields <= fields_.size());
|
||||
|
||||
// Calculate the required size for the new strings
|
||||
std::size_t size = 0;
|
||||
for (std::size_t i = first; i < first + num_fields; ++i)
|
||||
{
|
||||
size += get_string_size(fields_[i]);
|
||||
}
|
||||
|
||||
// Make space. The previous fields should be in offset form
|
||||
std::size_t old_string_buffer_size = string_buffer_.size();
|
||||
string_buffer_.resize(old_string_buffer_size + size);
|
||||
|
||||
// Copy strings and blobs
|
||||
std::size_t offset = old_string_buffer_size;
|
||||
for (std::size_t i = first; i < first + num_fields; ++i)
|
||||
{
|
||||
auto& f = fields_[i];
|
||||
switch (f.kind())
|
||||
{
|
||||
case field_kind::string: offset += copy_string_as_offset(string_buffer_.data(), offset, f); break;
|
||||
case field_kind::blob: offset += copy_blob_as_offset(string_buffer_.data(), offset, f); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
assert(offset == string_buffer_.size());
|
||||
}
|
||||
|
||||
#endif
|
101
include/boost/mysql/detail/auxiliar/results_iterator.hpp
Normal file
101
include/boost/mysql/detail/auxiliar/results_iterator.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// 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_RESULTS_ITERATOR_HPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_RESULTS_ITERATOR_HPP
|
||||
|
||||
#include <boost/mysql/resultset.hpp>
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class results_iterator
|
||||
{
|
||||
const execution_state_impl* self_{};
|
||||
std::size_t index_{};
|
||||
|
||||
public:
|
||||
using value_type = resultset;
|
||||
using reference = resultset_view;
|
||||
using pointer = resultset_view;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
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& operator++() noexcept
|
||||
{
|
||||
++index_;
|
||||
return *this;
|
||||
}
|
||||
results_iterator operator++(int) noexcept
|
||||
{
|
||||
auto res = *this;
|
||||
++(*this);
|
||||
return res;
|
||||
}
|
||||
results_iterator& operator--() noexcept
|
||||
{
|
||||
--index_;
|
||||
return *this;
|
||||
}
|
||||
results_iterator operator--(int) noexcept
|
||||
{
|
||||
auto res = *this;
|
||||
--(*this);
|
||||
return res;
|
||||
}
|
||||
results_iterator& operator+=(std::ptrdiff_t n) noexcept
|
||||
{
|
||||
index_ += n;
|
||||
return *this;
|
||||
}
|
||||
results_iterator& operator-=(std::ptrdiff_t n) noexcept
|
||||
{
|
||||
index_ -= n;
|
||||
return *this;
|
||||
}
|
||||
results_iterator operator+(std::ptrdiff_t n) const noexcept
|
||||
{
|
||||
return results_iterator(self_, index_ + n);
|
||||
}
|
||||
results_iterator operator-(std::ptrdiff_t n) const noexcept { return *this + (-n); }
|
||||
std::ptrdiff_t operator-(results_iterator rhs) const noexcept { return index_ - rhs.index_; }
|
||||
|
||||
pointer operator->() const noexcept { return **this; }
|
||||
reference operator*() const noexcept { return (*this)[0]; }
|
||||
reference operator[](std::ptrdiff_t i) const noexcept
|
||||
{
|
||||
return resultset_view_access::construct(*self_, index_ + i);
|
||||
}
|
||||
|
||||
bool operator==(results_iterator rhs) const noexcept { return index_ == rhs.index_; }
|
||||
bool operator!=(results_iterator rhs) const noexcept { return !(*this == rhs); }
|
||||
bool operator<(results_iterator rhs) const noexcept { return index_ < rhs.index_; }
|
||||
bool operator<=(results_iterator rhs) const noexcept { return index_ <= rhs.index_; }
|
||||
bool operator>(results_iterator rhs) const noexcept { return index_ > rhs.index_; }
|
||||
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_; }
|
||||
};
|
||||
|
||||
inline results_iterator operator+(std::ptrdiff_t n, results_iterator it) noexcept { return it + n; }
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -1,55 +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_ROW_BASE_HPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_ROW_BASE_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Base class providing implementation helpers for row and rows.
|
||||
// Models a field_view vector with strings pointing into a
|
||||
// single character buffer.
|
||||
class row_base
|
||||
{
|
||||
public:
|
||||
row_base() = default;
|
||||
inline row_base(const field_view* fields, std::size_t size);
|
||||
inline row_base(const row_base&);
|
||||
row_base(row_base&&) = default;
|
||||
inline row_base& operator=(const row_base&);
|
||||
row_base& operator=(row_base&&) = default;
|
||||
~row_base() = default;
|
||||
|
||||
inline void assign(const field_view* fields, std::size_t size);
|
||||
inline void copy_strings();
|
||||
inline void clear() noexcept
|
||||
{
|
||||
fields_.clear();
|
||||
string_buffer_.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<field_view> fields_;
|
||||
|
||||
private:
|
||||
std::vector<unsigned char> string_buffer_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/impl/row_base.ipp>
|
||||
|
||||
#endif
|
84
include/boost/mysql/detail/auxiliar/row_impl.hpp
Normal file
84
include/boost/mysql/detail/auxiliar/row_impl.hpp
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// 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_ROW_IMPL_HPP
|
||||
#define BOOST_MYSQL_DETAIL_AUXILIAR_ROW_IMPL_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
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)
|
||||
{
|
||||
std::size_t old_size = storage.size();
|
||||
storage.resize(old_size + num_fields);
|
||||
return storage.data() + old_size;
|
||||
}
|
||||
|
||||
// A field_view vector with strings pointing into a
|
||||
// single character buffer. Used to implement owning row types
|
||||
class row_impl
|
||||
{
|
||||
public:
|
||||
row_impl() = default;
|
||||
inline row_impl(const row_impl&);
|
||||
row_impl(row_impl&&) = default;
|
||||
inline row_impl& operator=(const row_impl&);
|
||||
row_impl& operator=(row_impl&&) = default;
|
||||
~row_impl() = default;
|
||||
|
||||
// Copies the given span into *this
|
||||
inline row_impl(const field_view* fields, std::size_t size);
|
||||
|
||||
// Copies the given span into *this, used by row/rows in assignment from view
|
||||
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)
|
||||
{
|
||||
return ::boost::mysql::detail::add_fields(fields_, num_fields);
|
||||
}
|
||||
|
||||
// Saves strings in the [first, first+num_fields) range into the string buffer, used by execute
|
||||
inline void copy_strings_as_offsets(std::size_t first, std::size_t num_fields);
|
||||
|
||||
// Restores any offsets into string views, used by execute
|
||||
inline void offsets_to_string_views()
|
||||
{
|
||||
for (auto& f : fields_)
|
||||
field_view_access::offset_to_string_view(f, string_buffer_.data());
|
||||
}
|
||||
|
||||
const std::vector<field_view>& fields() const noexcept { return fields_; }
|
||||
|
||||
inline void clear() noexcept
|
||||
{
|
||||
fields_.clear();
|
||||
string_buffer_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void copy_strings();
|
||||
|
||||
std::vector<field_view> fields_;
|
||||
std::vector<unsigned char> string_buffer_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/impl/row_impl.ipp>
|
||||
|
||||
#endif
|
@ -50,7 +50,6 @@ public:
|
||||
channel_base(std::size_t read_buffer_size) : reader_(read_buffer_size) {}
|
||||
|
||||
// Reading
|
||||
const std::uint8_t* buffer_first() const noexcept { return reader_.buffer_first(); }
|
||||
bool has_read_messages() const noexcept { return reader_.has_message(); }
|
||||
boost::asio::const_buffer next_read_message(std::uint8_t& seqnum, error_code& err) noexcept
|
||||
{
|
||||
@ -107,28 +106,25 @@ public:
|
||||
executor_type get_executor() { return stream_.get_executor(); }
|
||||
|
||||
// Reading
|
||||
void read_some(error_code& code, bool keep_messages = false)
|
||||
{
|
||||
return reader_.read_some(stream_, code, keep_messages);
|
||||
}
|
||||
void read_some(error_code& code) { return reader_.read_some(stream_, code); }
|
||||
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_some(CompletionToken&& token, bool keep_messages = false)
|
||||
async_read_some(CompletionToken&& token)
|
||||
{
|
||||
return reader_.async_read_some(stream_, std::forward<CompletionToken>(token), keep_messages);
|
||||
return reader_.async_read_some(stream_, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
boost::asio::const_buffer read_one(std::uint8_t& seqnum, error_code& ec, bool keep_messages = false)
|
||||
boost::asio::const_buffer read_one(std::uint8_t& seqnum, error_code& ec)
|
||||
{
|
||||
return reader_.read_one(stream_, seqnum, ec, keep_messages);
|
||||
return reader_.read_one(stream_, seqnum, ec);
|
||||
}
|
||||
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, ::boost::asio::const_buffer)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, ::boost::asio::const_buffer))
|
||||
async_read_one(std::uint8_t& seqnum, CompletionToken&& token, bool keep_messages = false)
|
||||
async_read_one(std::uint8_t& seqnum, CompletionToken&& token)
|
||||
{
|
||||
return reader_.async_read_one(stream_, seqnum, std::forward<CompletionToken>(token), keep_messages);
|
||||
return reader_.async_read_one(stream_, seqnum, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
// Writing
|
||||
|
@ -39,19 +39,14 @@ boost::asio::const_buffer boost::mysql::detail::message_reader::get_next_message
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::message_reader::read_some(
|
||||
Stream& stream,
|
||||
error_code& ec,
|
||||
bool keep_messages
|
||||
)
|
||||
void boost::mysql::detail::message_reader::read_some(Stream& stream, error_code& ec)
|
||||
{
|
||||
// If we already have a message, complete immediately
|
||||
if (has_message())
|
||||
return;
|
||||
|
||||
// Remove processed messages if we can
|
||||
if (!keep_messages)
|
||||
buffer_.remove_reserved();
|
||||
// Remove processed messages
|
||||
buffer_.remove_reserved();
|
||||
|
||||
while (!has_message())
|
||||
{
|
||||
@ -74,13 +69,9 @@ template <class Stream>
|
||||
struct boost::mysql::detail::message_reader::read_some_op : boost::asio::coroutine
|
||||
{
|
||||
message_reader& reader_;
|
||||
bool keep_messages_;
|
||||
Stream& stream_;
|
||||
|
||||
read_some_op(message_reader& reader, bool keep_messages, Stream& stream) noexcept
|
||||
: reader_(reader), keep_messages_(keep_messages), stream_(stream)
|
||||
{
|
||||
}
|
||||
read_some_op(message_reader& reader, Stream& stream) noexcept : reader_(reader), stream_(stream) {}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code ec = {}, std::size_t bytes_read = 0)
|
||||
@ -103,9 +94,8 @@ struct boost::mysql::detail::message_reader::read_some_op : boost::asio::corouti
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Remove processed messages if we can
|
||||
if (!keep_messages_)
|
||||
reader_.buffer_.remove_reserved();
|
||||
// Remove processed messages
|
||||
reader_.buffer_.remove_reserved();
|
||||
|
||||
while (!reader_.has_message())
|
||||
{
|
||||
@ -114,10 +104,7 @@ struct boost::mysql::detail::message_reader::read_some_op : boost::asio::corouti
|
||||
reader_.maybe_resize_buffer();
|
||||
|
||||
// Actually read bytes
|
||||
BOOST_ASIO_CORO_YIELD stream_.async_read_some(
|
||||
reader_.buffer_.free_area(),
|
||||
std::move(self)
|
||||
);
|
||||
BOOST_ASIO_CORO_YIELD stream_.async_read_some(reader_.buffer_.free_area(), std::move(self));
|
||||
valgrind_make_mem_defined(reader_.buffer_.free_first(), bytes_read);
|
||||
|
||||
// Process them
|
||||
@ -129,18 +116,12 @@ struct boost::mysql::detail::message_reader::read_some_op : boost::asio::corouti
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
template <class Stream, 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::detail::message_reader::async_read_some(
|
||||
Stream& stream,
|
||||
CompletionToken&& token,
|
||||
bool keep_messages
|
||||
)
|
||||
boost::mysql::detail::message_reader::async_read_some(Stream& stream, CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
read_some_op<Stream>{*this, keep_messages, stream},
|
||||
read_some_op<Stream>{*this, stream},
|
||||
token,
|
||||
stream
|
||||
);
|
||||
@ -150,11 +131,10 @@ template <class Stream>
|
||||
boost::asio::const_buffer boost::mysql::detail::message_reader::read_one(
|
||||
Stream& stream,
|
||||
std::uint8_t& seqnum,
|
||||
error_code& ec,
|
||||
bool keep_messages
|
||||
error_code& ec
|
||||
)
|
||||
{
|
||||
read_some(stream, ec, keep_messages);
|
||||
read_some(stream, ec);
|
||||
if (ec)
|
||||
return {};
|
||||
else
|
||||
@ -165,12 +145,11 @@ template <class Stream>
|
||||
struct boost::mysql::detail::message_reader::read_one_op : boost::asio::coroutine
|
||||
{
|
||||
message_reader& reader_;
|
||||
bool keep_messages_;
|
||||
Stream& stream_;
|
||||
std::uint8_t& seqnum_;
|
||||
|
||||
read_one_op(message_reader& reader, bool keep_messages, Stream& stream, std::uint8_t& seqnum)
|
||||
: reader_(reader), keep_messages_(keep_messages), stream_(stream), seqnum_(seqnum)
|
||||
read_one_op(message_reader& reader, Stream& stream, std::uint8_t& seqnum)
|
||||
: reader_(reader), stream_(stream), seqnum_(seqnum)
|
||||
{
|
||||
}
|
||||
|
||||
@ -188,7 +167,7 @@ struct boost::mysql::detail::message_reader::read_one_op : boost::asio::coroutin
|
||||
boost::asio::const_buffer b;
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD reader_.async_read_some(stream_, std::move(self), keep_messages_);
|
||||
BOOST_ASIO_CORO_YIELD reader_.async_read_some(stream_, std::move(self));
|
||||
b = reader_.get_next_message(seqnum_, code);
|
||||
self.complete(code, b);
|
||||
}
|
||||
@ -206,12 +185,11 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
boost::mysql::detail::message_reader::async_read_one(
|
||||
Stream& stream,
|
||||
std::uint8_t& seqnum,
|
||||
CompletionToken&& token,
|
||||
bool keep_messages
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, boost::asio::const_buffer)>(
|
||||
read_one_op<Stream>{*this, keep_messages, stream, seqnum},
|
||||
read_one_op<Stream>{*this, stream, seqnum},
|
||||
token,
|
||||
stream
|
||||
);
|
||||
|
@ -33,38 +33,31 @@ public:
|
||||
}
|
||||
|
||||
bool has_message() const noexcept { return result_.has_message; }
|
||||
const std::uint8_t* buffer_first() const noexcept { return buffer_.first(); }
|
||||
|
||||
inline boost::asio::const_buffer get_next_message(std::uint8_t& seqnum, error_code& ec) noexcept;
|
||||
|
||||
// Reads some messages from stream, until there is at least one
|
||||
// or an error happens. On success, has_message() returns true
|
||||
// and get_next_message() returns the parsed message.
|
||||
// May relocate the buffer, modifying buffer_first(). If !keep_messages,
|
||||
// the reserved area bytes will be removed before the actual read; otherwise,
|
||||
// they won't be touched (but may be reallocated).
|
||||
// May relocate the buffer, modifying buffer_first().
|
||||
// The reserved area bytes will be removed before the actual read.
|
||||
template <class Stream>
|
||||
void read_some(Stream& stream, error_code& ec, bool keep_messages = false);
|
||||
void read_some(Stream& stream, error_code& ec);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_some(Stream& stream, CompletionToken&& token, bool keep_messages = false);
|
||||
async_read_some(Stream& stream, CompletionToken&& token);
|
||||
|
||||
// Equivalent to read_some + get_next_message
|
||||
template <class Stream>
|
||||
boost::asio::const_buffer read_one(
|
||||
Stream& stream,
|
||||
std::uint8_t& seqnum,
|
||||
error_code& ec,
|
||||
bool keep_messages = false
|
||||
);
|
||||
boost::asio::const_buffer read_one(Stream& stream, std::uint8_t& seqnum, error_code& ec);
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, boost::asio::const_buffer))
|
||||
CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, boost::asio::const_buffer))
|
||||
async_read_one(Stream& stream, std::uint8_t& seqnum, CompletionToken&& token, bool keep_messages = false);
|
||||
async_read_one(Stream& stream, std::uint8_t& seqnum, CompletionToken&& token);
|
||||
|
||||
// Exposed for the sake of testing
|
||||
read_buffer& buffer() noexcept { return buffer_; }
|
||||
|
@ -5,15 +5,15 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_QUERY_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_QUERY_HPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
@ -21,21 +21,24 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// The caller function must serialize the execution request into channel's buffer
|
||||
// before calling these
|
||||
|
||||
template <class Stream>
|
||||
void start_query(
|
||||
void execute(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
resultset_encoding enc,
|
||||
results& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_query(
|
||||
async_execute(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
resultset_encoding enc,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
@ -44,6 +47,6 @@ async_start_query(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/start_query.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/impl/execute.hpp>
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_QUERY_HPP_ */
|
||||
#endif
|
@ -1,55 +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_EXECUTE_STATEMENT_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void execute_statement(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
results& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute_statement(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/execute_statement.hpp>
|
||||
|
||||
#endif
|
51
include/boost/mysql/detail/network_algorithms/helpers.hpp
Normal file
51
include/boost/mysql/detail/network_algorithms/helpers.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// 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
|
@ -5,12 +5,12 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_STATEMENT_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_STATEMENT_EXECUTION_HPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HIGH_LEVEL_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HIGH_LEVEL_EXECUTION_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
@ -22,6 +22,62 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
void query(channel<Stream>& channel, string_view query, results& output, error_code& err, diagnostics& diag);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
template <class Stream>
|
||||
void start_query(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void execute_statement(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
results& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_execute_statement(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
void start_statement_execution(
|
||||
channel<Stream>& channel,
|
||||
@ -76,6 +132,6 @@ async_start_statement_execution(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/start_statement_execution.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/impl/high_level_execution.hpp>
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP_ */
|
||||
#endif
|
159
include/boost/mysql/detail/network_algorithms/impl/execute.hpp
Normal file
159
include/boost/mysql/detail/network_algorithms/impl/execute.hpp
Normal file
@ -0,0 +1,159 @@
|
||||
//
|
||||
// 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_EXECUTE_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/execute.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/helpers.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
struct execute_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
resultset_encoding enc_;
|
||||
execution_state_impl& st_;
|
||||
diagnostics& diag_;
|
||||
|
||||
execute_op(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag
|
||||
) noexcept
|
||||
: chan_(chan), enc_(enc), st_(st), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
// Setup
|
||||
diag_.clear();
|
||||
st_.reset(enc_, nullptr); // rows owned by st
|
||||
|
||||
// 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));
|
||||
|
||||
while (!st_.complete())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD async_read_resultset_head(chan_, st_, diag_, std::move(self));
|
||||
|
||||
while (st_.should_read_rows())
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::execute(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
results& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
diag.clear();
|
||||
auto& st = results_access::get_impl(result);
|
||||
st.reset(enc, nullptr);
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
channel.write(channel.shared_buffer(), st.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
while (!st.complete())
|
||||
{
|
||||
if (st.should_read_head())
|
||||
{
|
||||
read_resultset_head(channel, st, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
while (st.should_read_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);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_execute(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
results& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
|
||||
execute_op<Stream>(chan, enc, results_access::get_impl(result), diag),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */
|
@ -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_EXECUTE_STATEMENT_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_STATEMENT_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/execute_statement.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_all_rows.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_statement_execution.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
struct execute_statement_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
statement stmt_;
|
||||
FieldLikeTuple params_;
|
||||
results& output_;
|
||||
|
||||
// We need a deduced context to enable perfect forwarding
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE DeducedTuple>
|
||||
execute_statement_op(
|
||||
channel<Stream>& chan,
|
||||
diagnostics& diag,
|
||||
const statement& stmt,
|
||||
DeducedTuple&& params,
|
||||
results& output
|
||||
) noexcept
|
||||
: chan_(chan), diag_(diag), stmt_(stmt), params_(std::forward<DeducedTuple>(params)), output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_start_statement_execution(
|
||||
chan_,
|
||||
stmt_,
|
||||
std::move(params_),
|
||||
results_access::get_state(output_),
|
||||
diag_,
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD async_read_all_rows(
|
||||
chan_,
|
||||
results_access::get_state(output_),
|
||||
results_access::get_rows(output_),
|
||||
diag_,
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void boost::mysql::detail::execute_statement(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
results& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
start_statement_execution(channel, stmt, params, results_access::get_state(output), err, diag);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
read_all_rows(channel, results_access::get_state(output), results_access::get_rows(output), err, diag);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
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::detail::async_execute_statement(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
using decayed_tuple = typename std::decay<FieldLikeTuple>::type;
|
||||
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
|
||||
execute_statement_op<Stream, decayed_tuple>(
|
||||
chan,
|
||||
diag,
|
||||
stmt,
|
||||
std::forward<FieldLikeTuple>(params),
|
||||
output
|
||||
),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */
|
@ -81,6 +81,7 @@ inline error_code process_capabilities(
|
||||
capabilities server_caps(handshake.capability_falgs);
|
||||
capabilities required_caps = mandatory_capabilities |
|
||||
conditional_capability(!params.database().empty(), CLIENT_CONNECT_WITH_DB) |
|
||||
conditional_capability(params.multi_queries(), CLIENT_MULTI_STATEMENTS) |
|
||||
conditional_capability(
|
||||
ssl == ssl_mode::require && is_ssl_stream,
|
||||
CLIENT_SSL
|
||||
|
@ -0,0 +1,432 @@
|
||||
//
|
||||
// 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_HIGH_LEVEL_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_HIGH_LEVEL_EXECUTION_HPP
|
||||
|
||||
#pragma once
|
||||
#include <boost/mysql/detail/network_algorithms/execute.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/high_level_execution.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution.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/asio/bind_executor.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Helpers
|
||||
inline void serialize_query_exec_req(channel_base& chan, string_view query)
|
||||
{
|
||||
com_query_packet request{string_eof(query)};
|
||||
serialize_message(request, chan.current_capabilities(), chan.shared_buffer());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
void serialize_stmt_exec_req(
|
||||
channel_base& chan,
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator first,
|
||||
FieldViewFwdIterator last
|
||||
)
|
||||
{
|
||||
com_stmt_execute_packet<FieldViewFwdIterator> request{
|
||||
stmt.id(),
|
||||
std::uint8_t(0), // flags
|
||||
std::uint32_t(1), // iteration count
|
||||
std::uint8_t(1), // new params flag: set
|
||||
first,
|
||||
last,
|
||||
};
|
||||
serialize_message(request, chan.current_capabilities(), chan.shared_buffer());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... 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))...}};
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... 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)>());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void serialize_stmt_exec_req(channel_base& chan, const statement& stmt, const FieldLikeTuple& params)
|
||||
{
|
||||
auto arr = tuple_to_array(params);
|
||||
serialize_stmt_exec_req(chan, stmt, arr.begin(), arr.end());
|
||||
}
|
||||
|
||||
inline error_code check_num_params(const statement& stmt, std::size_t param_count)
|
||||
{
|
||||
return param_count == stmt.num_params() ? error_code() : make_error_code(client_errc::wrong_num_params);
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
error_code check_num_params_it(
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last
|
||||
)
|
||||
{
|
||||
return check_num_params(stmt, std::distance(params_first, params_last));
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
error_code check_num_params_tuple(const statement& stmt, const FieldLikeTuple&)
|
||||
{
|
||||
return check_num_params(stmt, std::tuple_size<FieldLikeTuple>::value);
|
||||
}
|
||||
|
||||
template <class Stream, class Handler>
|
||||
void fast_fail(channel<Stream>& chan, Handler&& handler, error_code ec)
|
||||
{
|
||||
boost::asio::post(boost::asio::bind_executor(
|
||||
boost::asio::get_associated_executor(handler, chan.get_executor()),
|
||||
std::bind(std::forward<Handler>(handler), ec)
|
||||
));
|
||||
}
|
||||
|
||||
// Async initiations
|
||||
struct initiate_query
|
||||
{
|
||||
template <class Handler, class Stream>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
string_view query,
|
||||
results& result,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
serialize_query_exec_req(chan, query);
|
||||
async_execute(chan.get(), resultset_encoding::text, result, diag, std::forward<Handler>(handler));
|
||||
}
|
||||
};
|
||||
|
||||
struct initiate_start_query
|
||||
{
|
||||
template <class Handler, class Stream>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
string_view query,
|
||||
execution_state& st,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
serialize_query_exec_req(chan, query);
|
||||
async_start_execution(chan.get(), resultset_encoding::text, st, diag, std::forward<Handler>(handler));
|
||||
}
|
||||
};
|
||||
|
||||
struct initiate_execute_statement
|
||||
{
|
||||
template <class Handler, class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
results& result,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto ec = check_num_params_tuple(stmt, params);
|
||||
if (ec)
|
||||
{
|
||||
diag.clear();
|
||||
fast_fail(chan.get(), std::forward<Handler>(handler), ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
serialize_stmt_exec_req(chan, stmt, params);
|
||||
async_execute(
|
||||
chan.get(),
|
||||
resultset_encoding::binary,
|
||||
result,
|
||||
diag,
|
||||
std::forward<Handler>(handler)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct initiate_start_statement_execution
|
||||
{
|
||||
template <class Handler, class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
execution_state& st,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto ec = check_num_params_tuple(stmt, params);
|
||||
if (ec)
|
||||
{
|
||||
diag.clear();
|
||||
fast_fail(chan.get(), std::forward<Handler>(handler), ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
serialize_stmt_exec_req(chan, stmt, params);
|
||||
async_start_execution(
|
||||
chan.get(),
|
||||
resultset_encoding::binary,
|
||||
st,
|
||||
diag,
|
||||
std::forward<Handler>(handler)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Handler, class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FwdIt>
|
||||
void operator()(
|
||||
Handler&& handler,
|
||||
std::reference_wrapper<channel<Stream>> chan,
|
||||
const statement& stmt,
|
||||
FwdIt first,
|
||||
FwdIt last,
|
||||
execution_state& st,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto ec = check_num_params_it(stmt, first, last);
|
||||
if (ec)
|
||||
{
|
||||
diag.clear();
|
||||
fast_fail(chan.get(), std::forward<Handler>(handler), ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
serialize_stmt_exec_req(chan, stmt, first, last);
|
||||
async_start_execution(
|
||||
chan.get(),
|
||||
resultset_encoding::binary,
|
||||
st,
|
||||
diag,
|
||||
std::forward<Handler>(handler)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::query(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
results& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
serialize_query_exec_req(channel, query);
|
||||
execute(channel, resultset_encoding::text, result, err, diag);
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
results& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_initiate<CompletionToken, void(boost::mysql::error_code)>(
|
||||
initiate_query(),
|
||||
token,
|
||||
std::ref(chan),
|
||||
query,
|
||||
std::ref(result),
|
||||
std::ref(diag)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::start_query(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
serialize_query_exec_req(channel, query);
|
||||
start_execution(channel, resultset_encoding::text, st, err, diag);
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_start_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_initiate<CompletionToken, void(boost::mysql::error_code)>(
|
||||
initiate_start_query(),
|
||||
token,
|
||||
std::ref(chan),
|
||||
query,
|
||||
std::ref(st),
|
||||
std::ref(diag)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void boost::mysql::detail::execute_statement(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
results& result,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
err = check_num_params_tuple(stmt, params);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
serialize_stmt_exec_req(channel, stmt, params);
|
||||
execute(channel, resultset_encoding::binary, result, err, diag);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
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::detail::async_execute_statement(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
results& result,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_initiate<CompletionToken, void(boost::mysql::error_code)>(
|
||||
initiate_execute_statement(),
|
||||
token,
|
||||
std::ref(chan),
|
||||
stmt,
|
||||
std::forward<FieldLikeTuple>(params),
|
||||
std::ref(result),
|
||||
std::ref(diag)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void boost::mysql::detail::start_statement_execution(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
err = check_num_params_tuple(stmt, params);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
serialize_stmt_exec_req(channel, stmt, params);
|
||||
start_execution(channel, resultset_encoding::binary, st, err, diag);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
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::detail::async_start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
execution_state& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_initiate<CompletionToken, void(boost::mysql::error_code)>(
|
||||
initiate_start_statement_execution(),
|
||||
token,
|
||||
std::ref(chan),
|
||||
stmt,
|
||||
std::forward<FieldLikeTuple>(params),
|
||||
std::ref(output),
|
||||
std::ref(diag)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
void boost::mysql::detail::start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
err = check_num_params_it(stmt, params_first, params_last);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
serialize_stmt_exec_req(chan, stmt, params_first, params_last);
|
||||
start_execution(chan, resultset_encoding::binary, st, err, diag);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator,
|
||||
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::detail::async_start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_initiate<CompletionToken, void(boost::mysql::error_code)>(
|
||||
initiate_start_statement_execution(),
|
||||
token,
|
||||
std::ref(chan),
|
||||
stmt,
|
||||
params_first,
|
||||
params_last,
|
||||
std::ref(st),
|
||||
std::ref(diag)
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */
|
@ -1,100 +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_QUERY_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_QUERY_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/query.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/read_all_rows.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_query.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
struct query_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
string_view query_;
|
||||
results& output_;
|
||||
|
||||
query_op(channel<Stream>& chan, diagnostics& diag, string_view q, results& output) noexcept
|
||||
: chan_(chan), diag_(diag), query_(q), output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_start_query(chan_, query_, results_access::get_state(output_), diag_, std::move(self));
|
||||
|
||||
BOOST_ASIO_CORO_YIELD async_read_all_rows(
|
||||
chan_,
|
||||
results_access::get_state(output_),
|
||||
results_access::get_rows(output_),
|
||||
diag_,
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::query(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
results& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
start_query(channel, query, results_access::get_state(output), err, diag);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
read_all_rows(channel, results_access::get_state(output), results_access::get_rows(output), err, diag);
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
results& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
|
||||
query_op<Stream>(chan, diag, query, output),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */
|
@ -53,7 +53,7 @@ struct quit_connection_op : boost::asio::coroutine
|
||||
}
|
||||
|
||||
// SSL shutdown error ignored, as MySQL doesn't always gracefully
|
||||
// close SSL connections. TODO: was this because of a missing if?
|
||||
// close SSL connections.
|
||||
if (chan_.stream().ssl_active())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD chan_.stream().async_shutdown(std::move(self));
|
||||
|
@ -1,195 +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_ALL_ROWS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_ALL_ROWS_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/read_all_rows.hpp>
|
||||
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
inline void process_all_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
rows& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Process all read messages until they run out, an error happens
|
||||
// or an EOF is received
|
||||
while (channel.has_read_messages())
|
||||
{
|
||||
// Get the row message
|
||||
auto message = channel.next_read_message(execution_state_access::get_sequence_number(st), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Deserialize the row. Values are stored in a vector owned by the channel
|
||||
deserialize_row(
|
||||
message,
|
||||
channel.current_capabilities(),
|
||||
channel.flavor(),
|
||||
channel.buffer_first(),
|
||||
st,
|
||||
channel.shared_fields(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// If we received an EOF, we're done
|
||||
if (st.complete())
|
||||
{
|
||||
offsets_to_string_views(channel.shared_fields(), channel.buffer_first());
|
||||
output = rows_view_access::construct(
|
||||
channel.shared_fields().data(),
|
||||
channel.shared_fields().size(),
|
||||
st.meta().size()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
struct read_all_rows_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
execution_state& st_;
|
||||
rows& output_;
|
||||
|
||||
read_all_rows_op(channel<Stream>& chan, diagnostics& diag, execution_state& st, rows& output) noexcept
|
||||
: chan_(chan), diag_(diag), st_(st), output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
diag_.clear();
|
||||
rows_access::clear(output_);
|
||||
|
||||
// If the op is already complete, we don't need to read anything
|
||||
if (st_.complete())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
self.complete(error_code());
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Clear anything from previous runs
|
||||
chan_.shared_fields().clear();
|
||||
|
||||
// Read at least one message
|
||||
while (!st_.complete())
|
||||
{
|
||||
// Actually read
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self), true);
|
||||
|
||||
// Process messages
|
||||
process_all_rows(chan_, st_, output_, err, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
}
|
||||
|
||||
// Done
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::read_all_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
rows& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
diag.clear();
|
||||
rows_access::clear(output);
|
||||
|
||||
// If the op is already complete, we don't need to read anything
|
||||
if (st.complete())
|
||||
{
|
||||
err = error_code();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear anything from previous runs
|
||||
channel.shared_fields().clear();
|
||||
|
||||
while (!st.complete())
|
||||
{
|
||||
// Read from the stream until there is at least one message
|
||||
channel.read_some(err, true);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Process read messages
|
||||
process_all_rows(channel, st, output, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_read_all_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
rows& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
read_all_rows_op<Stream>(channel, diag, st, output),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_IPP_ */
|
@ -0,0 +1,223 @@
|
||||
//
|
||||
// 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_RESULTSET_HEAD_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_READ_RESULTSET_HEAD_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.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/process_error_packet.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class read_resultset_head_processor
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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{};
|
||||
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_; }
|
||||
};
|
||||
|
||||
template <class Stream>
|
||||
struct read_resultset_head_op : boost::asio::coroutine
|
||||
{
|
||||
read_resultset_head_processor processor_;
|
||||
|
||||
read_resultset_head_op(channel<Stream>& chan, execution_state_impl& st, diagnostics& diag)
|
||||
: processor_(chan, st, 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 = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-error path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
processor_.clear_diag();
|
||||
|
||||
// If we're not reading head, return
|
||||
if (!processor_.state().should_read_head())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
self.complete(error_code());
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Read the response
|
||||
BOOST_ASIO_CORO_YIELD get_channel().async_read_one(
|
||||
processor_.state().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);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Read all of the field definitions
|
||||
while (processor_.state().should_read_meta())
|
||||
{
|
||||
// Read from the stream if we need it
|
||||
if (!get_channel().has_read_messages())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD get_channel().async_read_some(std::move(self));
|
||||
}
|
||||
|
||||
// Process the metadata packet
|
||||
err = processor_.process_field_definition();
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
}
|
||||
|
||||
// No EOF packet is expected here, as we require deprecate EOF capabilities
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::read_resultset_head(
|
||||
channel<Stream>& chan,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
read_resultset_head_processor processor(chan, st, diag);
|
||||
processor.clear_diag();
|
||||
|
||||
// If we're not reading head, return
|
||||
if (!st.should_read_head())
|
||||
return;
|
||||
|
||||
// Read the response
|
||||
auto msg = chan.read_one(st.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);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read all of the field definitions (zero if empty resultset)
|
||||
while (st.should_read_meta())
|
||||
{
|
||||
// Read from the stream if required
|
||||
if (!chan.has_read_messages())
|
||||
{
|
||||
chan.read_some(err);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the packet
|
||||
err = processor.process_field_definition();
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
read_resultset_head_op<Stream>(channel, st, diag),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
@ -9,12 +9,13 @@
|
||||
#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>
|
||||
|
||||
@ -27,64 +28,14 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
inline rows_view process_some_rows(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Process all read messages until they run out, an error happens
|
||||
// or an EOF is received
|
||||
std::size_t num_rows = 0;
|
||||
channel.shared_fields().clear();
|
||||
while (channel.has_read_messages())
|
||||
{
|
||||
// Get the row message
|
||||
auto message = channel.next_read_message(execution_state_access::get_sequence_number(st), err);
|
||||
if (err)
|
||||
return rows_view();
|
||||
|
||||
// Deserialize the row. Values are stored in a vector owned by the channel
|
||||
deserialize_row(
|
||||
message,
|
||||
channel.current_capabilities(),
|
||||
channel.flavor(),
|
||||
channel.buffer_first(),
|
||||
st,
|
||||
channel.shared_fields(),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
if (err)
|
||||
return rows_view();
|
||||
|
||||
// There is no need to copy strings values anywhere; the returned values
|
||||
// will point into the channel's internal buffer
|
||||
|
||||
// If we received an EOF, we're done
|
||||
if (st.complete())
|
||||
break;
|
||||
++num_rows;
|
||||
}
|
||||
offsets_to_string_views(channel.shared_fields(), channel.buffer_first());
|
||||
|
||||
return rows_view_access::construct(
|
||||
channel.shared_fields().data(),
|
||||
num_rows * st.meta().size(),
|
||||
st.meta().size()
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
struct read_some_rows_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
diagnostics& diag_;
|
||||
execution_state& st_;
|
||||
execution_state_impl& st_;
|
||||
|
||||
read_some_rows_op(channel<Stream>& chan, diagnostics& diag, execution_state& st) noexcept
|
||||
read_some_rows_op(channel<Stream>& chan, diagnostics& diag, execution_state_impl& st) noexcept
|
||||
: chan_(chan), diag_(diag), st_(st)
|
||||
{
|
||||
}
|
||||
@ -100,13 +51,12 @@ struct read_some_rows_op : boost::asio::coroutine
|
||||
}
|
||||
|
||||
// Normal path
|
||||
rows_view output;
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
diag_.clear();
|
||||
|
||||
// If the op is already complete, we don't need to read anything
|
||||
if (st_.complete())
|
||||
// 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());
|
||||
@ -117,9 +67,14 @@ struct read_some_rows_op : boost::asio::coroutine
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
|
||||
// Process messages
|
||||
output = process_some_rows(chan_, st_, err, diag_);
|
||||
process_available_rows(chan_, st_, err, diag_);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err, rows_view());
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
self.complete(err, output);
|
||||
self.complete(error_code(), st_.get_external_rows());
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -136,8 +91,10 @@ boost::mysql::rows_view boost::mysql::detail::read_some_rows(
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// If the op is already complete, we don't need to read anything
|
||||
if (st.complete())
|
||||
auto& impl = execution_state_access::get_impl(st);
|
||||
|
||||
// If we are not reading rows, just return
|
||||
if (!impl.should_read_rows())
|
||||
{
|
||||
return rows_view();
|
||||
}
|
||||
@ -148,7 +105,11 @@ boost::mysql::rows_view boost::mysql::detail::read_some_rows(
|
||||
return rows_view();
|
||||
|
||||
// Process read messages
|
||||
return process_some_rows(channel, st, err, diag);
|
||||
process_available_rows(channel, impl, err, diag);
|
||||
if (err)
|
||||
return rows_view();
|
||||
|
||||
return impl.get_external_rows();
|
||||
}
|
||||
|
||||
template <
|
||||
@ -164,7 +125,7 @@ boost::mysql::detail::async_read_some_rows(
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
|
||||
read_some_rows_op<Stream>(channel, diag, st),
|
||||
read_some_rows_op<Stream>(channel, diag, execution_state_access::get_impl(st)),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
|
@ -0,0 +1,118 @@
|
||||
//
|
||||
// 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_START_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_START_EXECUTION_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/read_resultset_head.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
struct start_execution_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
resultset_encoding enc_;
|
||||
execution_state_impl& st_;
|
||||
diagnostics& diag_;
|
||||
|
||||
start_execution_op(
|
||||
channel<Stream>& chan,
|
||||
resultset_encoding enc,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag
|
||||
)
|
||||
: chan_(chan), enc_(enc), st_(st), diag_(diag)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-error path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
// Setup
|
||||
diag_.clear();
|
||||
st_.reset(enc_, &chan_.shared_fields());
|
||||
|
||||
// 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));
|
||||
|
||||
// Read the first resultset's head
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_read_resultset_head(chan_, st_, diag_, std::move(self));
|
||||
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::start_execution(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Setup
|
||||
auto& impl = execution_state_access::get_impl(st);
|
||||
diag.clear();
|
||||
|
||||
impl.reset(enc, &channel.shared_fields());
|
||||
|
||||
// Send the execution request (already serialized at this point)
|
||||
channel.write(channel.shared_buffer(), impl.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read the first resultset's head
|
||||
read_resultset_head(channel, impl, err, diag);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_start_execution(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding enc,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
start_execution_op<Stream>(channel, enc, execution_state_access::get_impl(st), diag),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,319 +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_START_EXECUTION_GENERIC_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_START_EXECUTION_GENERIC_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/bytestring.hpp>
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution_generic.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/process_error_packet.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/protocol/serialization.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class start_execution_processor
|
||||
{
|
||||
channel_base& chan_;
|
||||
resultset_encoding encoding_;
|
||||
execution_state& st_;
|
||||
diagnostics& diag_;
|
||||
std::size_t num_fields_{};
|
||||
std::size_t remaining_fields_{};
|
||||
|
||||
public:
|
||||
start_execution_processor(
|
||||
channel_base& chan,
|
||||
resultset_encoding encoding,
|
||||
execution_state& st,
|
||||
diagnostics& diag
|
||||
) noexcept
|
||||
: chan_(chan), encoding_(encoding), st_(st), diag_(diag){};
|
||||
|
||||
void clear_diagnostics() noexcept { diag_.clear(); }
|
||||
|
||||
template <BOOST_MYSQL_SERIALIZE_FN SerializeFn>
|
||||
void process_request(SerializeFn&& request)
|
||||
{
|
||||
execution_state_access::reset(st_, encoding_);
|
||||
std::forward<SerializeFn>(request)(chan_.current_capabilities(), chan_.shared_buffer());
|
||||
}
|
||||
|
||||
void 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:
|
||||
execution_state_access::complete(st_, response.data.ok_pack);
|
||||
num_fields_ = 0;
|
||||
break;
|
||||
case execute_response::type_t::num_fields:
|
||||
num_fields_ = response.data.num_fields;
|
||||
remaining_fields_ = num_fields_;
|
||||
execution_state_access::prepare_meta(st_, num_fields_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error_code process_field_definition(boost::asio::const_buffer message)
|
||||
{
|
||||
column_definition_packet field_definition{};
|
||||
deserialization_context ctx(message, chan_.current_capabilities());
|
||||
auto err = deserialize_message(ctx, field_definition);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
execution_state_access::add_meta(st_, field_definition, chan_.meta_mode());
|
||||
--remaining_fields_;
|
||||
return error_code();
|
||||
}
|
||||
|
||||
std::uint8_t& sequence_number() noexcept { return execution_state_access::get_sequence_number(st_); }
|
||||
std::size_t num_fields() const noexcept { return num_fields_; }
|
||||
bool has_remaining_fields() const noexcept { return remaining_fields_ != 0; }
|
||||
};
|
||||
|
||||
template <class Stream, BOOST_MYSQL_SERIALIZE_FN SerializeFn>
|
||||
struct start_execution_generic_op : boost::asio::coroutine
|
||||
{
|
||||
channel<Stream>& chan_;
|
||||
SerializeFn serialize_fn_;
|
||||
start_execution_processor processor_;
|
||||
|
||||
template <class DeducedSerializeFn>
|
||||
start_execution_generic_op(
|
||||
channel<Stream>& chan,
|
||||
DeducedSerializeFn&& fn,
|
||||
const start_execution_processor& processor
|
||||
)
|
||||
: chan_(chan), serialize_fn_(std::forward<DeducedSerializeFn>(fn)), processor_(processor)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, error_code err = {}, boost::asio::const_buffer read_message = {})
|
||||
{
|
||||
// Error checking
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-error path
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
processor_.clear_diagnostics();
|
||||
|
||||
// Serialize the request
|
||||
processor_.process_request(std::move(serialize_fn_));
|
||||
|
||||
// Send it
|
||||
BOOST_ASIO_CORO_YIELD chan_
|
||||
.async_write(chan_.shared_buffer(), processor_.sequence_number(), std::move(self));
|
||||
|
||||
// Read the response
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_one(processor_.sequence_number(), std::move(self));
|
||||
|
||||
// Response may be: ok_packet, err_packet, local infile request
|
||||
// (not implemented), or response with fields
|
||||
processor_.process_response(read_message, err);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Read all of the field definitions
|
||||
while (processor_.has_remaining_fields())
|
||||
{
|
||||
// Read from the stream if we need it
|
||||
if (!chan_.has_read_messages())
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
|
||||
}
|
||||
|
||||
// Read the field definition packet
|
||||
read_message = chan_.next_read_message(processor_.sequence_number(), err);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
|
||||
// Process the message
|
||||
err = processor_.process_field_definition(read_message);
|
||||
if (err)
|
||||
{
|
||||
self.complete(err);
|
||||
BOOST_ASIO_CORO_YIELD break;
|
||||
}
|
||||
}
|
||||
|
||||
// No EOF packet is expected here, as we require deprecate EOF capabilities
|
||||
self.complete(error_code());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
inline boost::mysql::detail::execute_response boost::mysql::detail::deserialize_execute_response(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
) noexcept
|
||||
{
|
||||
// Response may be: ok_packet, err_packet, local infile request (not implemented)
|
||||
// If it is none of this, then the message type itself is the beginning of
|
||||
// a length-encoded int containing the field count
|
||||
deserialization_context ctx(msg, caps);
|
||||
std::uint8_t msg_type = 0;
|
||||
auto err = deserialize_message_part(ctx, msg_type);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (msg_type == ok_packet_header)
|
||||
{
|
||||
ok_packet ok_pack;
|
||||
err = deserialize_message(ctx, ok_pack);
|
||||
if (err)
|
||||
return err;
|
||||
return ok_pack;
|
||||
}
|
||||
else if (msg_type == error_packet_header)
|
||||
{
|
||||
return process_error_packet(ctx, flavor, diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resultset with metadata. First packet is an int_lenenc with
|
||||
// the number of field definitions to expect. Message type is part
|
||||
// of this packet, so we must rewind the context
|
||||
ctx.rewind(1);
|
||||
int_lenenc num_fields;
|
||||
err = deserialize_message(ctx, num_fields);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// We should have at least one field.
|
||||
// The max number of fields is some value around 1024. For simplicity/extensibility,
|
||||
// we accept anything less than 0xffff
|
||||
if (num_fields.value == 0 || num_fields.value > 0xffffu)
|
||||
{
|
||||
return make_error_code(client_errc::protocol_value_error);
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(num_fields.value);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Stream, BOOST_MYSQL_SERIALIZE_FN SerializeFn>
|
||||
void boost::mysql::detail::start_execution_generic(
|
||||
resultset_encoding encoding,
|
||||
channel<Stream>& channel,
|
||||
const SerializeFn& fn,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
// Clear info
|
||||
diag.clear();
|
||||
|
||||
// Serialize the request
|
||||
start_execution_processor processor(channel, encoding, st, diag);
|
||||
processor.process_request(fn);
|
||||
|
||||
// Send it
|
||||
channel.write(channel.shared_buffer(), processor.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read the response
|
||||
auto read_buffer = channel.read_one(processor.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Response may be: ok_packet, err_packet, local infile request (not implemented), or response
|
||||
// with fields
|
||||
processor.process_response(read_buffer, err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Read all of the field definitions (zero if empty resultset)
|
||||
while (processor.has_remaining_fields())
|
||||
{
|
||||
// Read from the stream if required
|
||||
if (!channel.has_read_messages())
|
||||
{
|
||||
channel.read_some(err);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the field definition packet
|
||||
read_buffer = channel.next_read_message(processor.sequence_number(), err);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
// Process the message
|
||||
err = processor.process_field_definition(read_buffer);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_SERIALIZE_FN SerializeFn,
|
||||
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::detail::async_start_execution_generic(
|
||||
resultset_encoding encoding,
|
||||
channel<Stream>& channel,
|
||||
SerializeFn&& fn,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
start_execution_generic_op<Stream, typename std::decay<SerializeFn>::type>(
|
||||
channel,
|
||||
std::forward<SerializeFn>(fn),
|
||||
start_execution_processor(channel, encoding, st, diag)
|
||||
),
|
||||
token,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,76 +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_START_QUERY_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_START_QUERY_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution_generic.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_query.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/protocol_types.hpp>
|
||||
#include <boost/mysql/detail/protocol/query_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/protocol/serialization.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
class query_serialize_fn
|
||||
{
|
||||
string_view query_;
|
||||
|
||||
public:
|
||||
query_serialize_fn(string_view query) noexcept : query_(query) {}
|
||||
void operator()(capabilities caps, std::vector<std::uint8_t>& buffer) const
|
||||
{
|
||||
com_query_packet request{string_eof(query_)};
|
||||
serialize_message(request, caps, buffer);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::detail::start_query(
|
||||
channel<Stream>& channel,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
start_execution_generic(resultset_encoding::text, channel, query_serialize_fn(query), output, err, diag);
|
||||
}
|
||||
|
||||
template <class Stream, 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::detail::async_start_query(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
execution_state& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return async_start_execution_generic(
|
||||
resultset_encoding::text,
|
||||
chan,
|
||||
query_serialize_fn(query),
|
||||
output,
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */
|
@ -1,269 +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_START_STATEMENT_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_START_STATEMENT_EXECUTION_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#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/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/stringize.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_execution_generic.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_statement_execution.hpp>
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
#include <boost/mysql/detail/protocol/prepared_statement_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/detail/protocol/serialization.hpp>
|
||||
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
com_stmt_execute_packet<FieldViewFwdIterator> make_stmt_execute_packet(
|
||||
std::uint32_t stmt_id,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last
|
||||
) noexcept(std::is_nothrow_copy_constructible<FieldViewFwdIterator>::value)
|
||||
{
|
||||
return com_stmt_execute_packet<FieldViewFwdIterator>{
|
||||
stmt_id,
|
||||
std::uint8_t(0), // flags
|
||||
std::uint32_t(1), // iteration count
|
||||
std::uint8_t(1), // new params flag: set
|
||||
params_first,
|
||||
params_last,
|
||||
};
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... 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))...}};
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE... 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)>());
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
class stmt_execute_it_serialize_fn
|
||||
{
|
||||
std::uint32_t stmt_id_;
|
||||
FieldViewFwdIterator first_;
|
||||
FieldViewFwdIterator last_;
|
||||
|
||||
public:
|
||||
stmt_execute_it_serialize_fn(
|
||||
std::uint32_t stmt_id,
|
||||
FieldViewFwdIterator first,
|
||||
FieldViewFwdIterator last
|
||||
) noexcept(std::is_nothrow_copy_constructible<FieldViewFwdIterator>::value)
|
||||
: stmt_id_(stmt_id), first_(first), last_(last)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(capabilities caps, std::vector<std::uint8_t>& buffer) const
|
||||
{
|
||||
auto request = make_stmt_execute_packet(stmt_id_, first_, last_);
|
||||
serialize_message(request, caps, buffer);
|
||||
}
|
||||
};
|
||||
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
class stmt_execute_tuple_serialize_fn
|
||||
{
|
||||
std::uint32_t stmt_id_;
|
||||
FieldLikeTuple params_;
|
||||
|
||||
public:
|
||||
// We need a deduced context to enable perfect forwarding
|
||||
template <BOOST_MYSQL_FIELD_LIKE_TUPLE DeducedTuple>
|
||||
stmt_execute_tuple_serialize_fn(std::uint32_t stmt_id, DeducedTuple&& params) noexcept(
|
||||
std::is_nothrow_constructible<FieldLikeTuple, decltype(std::forward<DeducedTuple>(params))>::value
|
||||
)
|
||||
: stmt_id_(stmt_id), params_(std::forward<DeducedTuple>(params))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(capabilities caps, std::vector<std::uint8_t>& buffer) const
|
||||
{
|
||||
auto field_views = tuple_to_array(params_);
|
||||
auto request = make_stmt_execute_packet(stmt_id_, field_views.begin(), field_views.end());
|
||||
serialize_message(request, caps, buffer);
|
||||
}
|
||||
};
|
||||
|
||||
inline error_code check_num_params(const statement& stmt, std::size_t param_count)
|
||||
{
|
||||
if (param_count != stmt.num_params())
|
||||
{
|
||||
return make_error_code(client_errc::wrong_num_params);
|
||||
}
|
||||
else
|
||||
{
|
||||
return error_code();
|
||||
}
|
||||
}
|
||||
|
||||
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
error_code check_num_params(
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last
|
||||
)
|
||||
{
|
||||
return check_num_params(stmt, std::distance(params_first, params_last));
|
||||
}
|
||||
|
||||
struct fast_fail_op : boost::asio::coroutine
|
||||
{
|
||||
error_code err_;
|
||||
diagnostics& diag_;
|
||||
|
||||
fast_fail_op(error_code err, diagnostics& diag) noexcept : err_(err), diag_(diag) {}
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
|
||||
diag_.clear();
|
||||
self.complete(err_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
|
||||
void boost::mysql::detail::start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last,
|
||||
execution_state& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
err = check_num_params(stmt, params_first, params_last);
|
||||
if (!err)
|
||||
{
|
||||
start_execution_generic(
|
||||
resultset_encoding::binary,
|
||||
chan,
|
||||
stmt_execute_it_serialize_fn<FieldViewFwdIterator>(stmt.id(), params_first, params_last),
|
||||
output,
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator,
|
||||
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::detail::async_start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldViewFwdIterator params_first,
|
||||
FieldViewFwdIterator params_last,
|
||||
execution_state& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
error_code err = check_num_params(stmt, params_first, params_last);
|
||||
if (err)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
fast_fail_op(err, diag),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
return async_start_execution_generic(
|
||||
resultset_encoding::binary,
|
||||
chan,
|
||||
stmt_execute_it_serialize_fn<FieldViewFwdIterator>(stmt.id(), params_first, params_last),
|
||||
output,
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
|
||||
void boost::mysql::detail::start_statement_execution(
|
||||
channel<Stream>& channel,
|
||||
const statement& stmt,
|
||||
const FieldLikeTuple& params,
|
||||
execution_state& output,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
auto params_array = tuple_to_array(params);
|
||||
start_statement_execution(channel, stmt, params_array.begin(), params_array.end(), output, err, diag);
|
||||
}
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
|
||||
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::detail::async_start_statement_execution(
|
||||
channel<Stream>& chan,
|
||||
const statement& stmt,
|
||||
FieldLikeTuple&& params,
|
||||
execution_state& output,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
using decayed_tuple = typename std::decay<FieldLikeTuple>::type;
|
||||
error_code err = check_num_params(stmt, std::tuple_size<decayed_tuple>::value);
|
||||
if (err)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code)>(
|
||||
fast_fail_op(err, diag),
|
||||
token,
|
||||
chan
|
||||
);
|
||||
}
|
||||
return async_start_execution_generic(
|
||||
resultset_encoding::binary,
|
||||
chan,
|
||||
stmt_execute_tuple_serialize_fn<decayed_tuple>(stmt.id(), std::forward<FieldLikeTuple>(params)),
|
||||
output,
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_STATEMENT_HPP_ */
|
@ -5,36 +5,33 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ALL_ROWS_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ALL_ROWS_HPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_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/metadata.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
template <class Stream>
|
||||
void read_all_rows(
|
||||
void read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
rows& output,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_read_all_rows(
|
||||
async_read_resultset_head(
|
||||
channel<Stream>& channel,
|
||||
execution_state& st,
|
||||
rows& output,
|
||||
execution_state_impl& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
@ -43,6 +40,6 @@ async_read_all_rows(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/read_all_rows.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/impl/read_resultset_head.hpp>
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_TEXT_ROW_HPP_ */
|
||||
#endif
|
@ -5,15 +5,15 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUERY_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUERY_HPP
|
||||
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_EXECUTION_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_EXECUTION_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
@ -21,15 +21,24 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// The caller function must serialize the execution request into channel's buffer
|
||||
// before calling these
|
||||
|
||||
template <class Stream>
|
||||
void query(channel<Stream>& channel, string_view query, results& output, error_code& err, diagnostics& diag);
|
||||
void start_execution(
|
||||
channel<Stream>& channel,
|
||||
resultset_encoding encoding,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
template <class Stream, BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_query(
|
||||
async_start_execution(
|
||||
channel<Stream>& chan,
|
||||
string_view query,
|
||||
results& output,
|
||||
resultset_encoding encoding,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
@ -38,6 +47,6 @@ async_query(
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/query.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/impl/start_execution.hpp>
|
||||
|
||||
#endif
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP_ */
|
@ -1,118 +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_START_EXECUTION_GENERIC_HPP
|
||||
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_EXECUTION_GENERIC_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/error_code.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
|
||||
#include <boost/mysql/detail/channel/channel.hpp>
|
||||
#include <boost/mysql/detail/config.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/prepared_statement_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/query_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
#include <concepts>
|
||||
#endif
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Exposed 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;
|
||||
|
||||
#ifdef BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
template <class T>
|
||||
concept serialize_fn = std::invocable<std::decay_t<T>, capabilities, std::vector<std::uint8_t>&>;
|
||||
|
||||
#define BOOST_MYSQL_SERIALIZE_FN ::boost::mysql::detail::serialize_fn
|
||||
|
||||
#else // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
#define BOOST_MYSQL_SERIALIZE_FN class
|
||||
|
||||
#endif // BOOST_MYSQL_HAS_CONCEPTS
|
||||
|
||||
// The sync version gets passed directlty the request packet to be serialized.
|
||||
// There is no need to defer the serialization here.
|
||||
template <class Stream, BOOST_MYSQL_SERIALIZE_FN SerializeFn>
|
||||
void start_execution_generic(
|
||||
resultset_encoding encoding,
|
||||
channel<Stream>& channel,
|
||||
const SerializeFn& fn,
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
// The async version gets passed a request maker, holding enough data to create
|
||||
// the request packet when the async op is started. Used by statement execution
|
||||
// with tuple params, to make lifetimes more user-friendly when using deferred tokens.
|
||||
template <
|
||||
class Stream,
|
||||
BOOST_MYSQL_SERIALIZE_FN SerializeFn,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
|
||||
async_start_execution_generic(
|
||||
resultset_encoding encoding,
|
||||
channel<Stream>& chan,
|
||||
SerializeFn&& fn,
|
||||
execution_state& st,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/detail/network_algorithms/impl/start_execution_generic.hpp>
|
||||
|
||||
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP_ */
|
@ -67,14 +67,8 @@ public:
|
||||
{
|
||||
return capabilities(value_ & rhs.value_);
|
||||
}
|
||||
constexpr bool operator==(const capabilities& rhs) const noexcept
|
||||
{
|
||||
return value_ == rhs.value_;
|
||||
}
|
||||
constexpr bool operator!=(const capabilities& rhs) const noexcept
|
||||
{
|
||||
return value_ != rhs.value_;
|
||||
}
|
||||
constexpr bool operator==(const capabilities& rhs) const noexcept { return value_ == rhs.value_; }
|
||||
constexpr bool operator!=(const capabilities& rhs) const noexcept { return value_ != rhs.value_; }
|
||||
};
|
||||
|
||||
/*
|
||||
@ -127,7 +121,7 @@ constexpr capabilities mandatory_capabilities{
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
constexpr capabilities optional_capabilities{0};
|
||||
constexpr capabilities optional_capabilities{CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
|
@ -24,7 +24,6 @@ namespace detail {
|
||||
inline deserialize_errc deserialize_binary_field(
|
||||
deserialization_context& ctx,
|
||||
const metadata& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
field_view& output
|
||||
);
|
||||
|
||||
|
@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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
|
@ -12,10 +12,12 @@
|
||||
#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>
|
||||
|
||||
@ -27,13 +29,11 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
// Exposed here for the sake of testing
|
||||
inline void deserialize_row(
|
||||
resultset_encoding encoding,
|
||||
deserialization_context& ctx,
|
||||
const std::vector<metadata>& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
std::vector<field_view>& output,
|
||||
metadata_collection_view meta,
|
||||
field_view* output, // Should point to meta.size() field_view objects
|
||||
error_code& err
|
||||
);
|
||||
|
||||
@ -41,22 +41,11 @@ inline void deserialize_row(
|
||||
boost::asio::const_buffer read_message,
|
||||
capabilities current_capabilities,
|
||||
db_flavor flavor,
|
||||
const std::uint8_t* buffer_first, // to store strings as offsets and allow buffer reallocation
|
||||
execution_state& st, // should be !complete()
|
||||
std::vector<field_view>& output,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
);
|
||||
|
||||
inline void offsets_to_string_views(
|
||||
std::vector<field_view>& fields,
|
||||
const std::uint8_t* buffer_first
|
||||
) noexcept
|
||||
{
|
||||
for (auto& f : fields)
|
||||
field_view_access::offset_to_string_view(f, buffer_first);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
@ -21,12 +21,7 @@ namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
inline deserialize_errc deserialize_text_field(
|
||||
string_view from,
|
||||
const metadata& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
field_view& output
|
||||
);
|
||||
inline deserialize_errc deserialize_text_field(string_view from, const metadata& meta, field_view& output);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
|
438
include/boost/mysql/detail/protocol/execution_state_impl.hpp
Normal file
438
include/boost/mysql/detail/protocol/execution_state_impl.hpp
Normal file
@ -0,0 +1,438 @@
|
||||
//
|
||||
// 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
|
@ -29,7 +29,6 @@ namespace detail {
|
||||
inline deserialize_errc deserialize_binary_field_string(
|
||||
deserialization_context& ctx,
|
||||
field_view& output,
|
||||
const std::uint8_t* buffer_first,
|
||||
bool is_blob
|
||||
) noexcept
|
||||
{
|
||||
@ -37,10 +36,16 @@ inline deserialize_errc deserialize_binary_field_string(
|
||||
auto err = deserialize(ctx, deser);
|
||||
if (err != deserialize_errc::ok)
|
||||
return err;
|
||||
output = detail::field_view_access::construct(
|
||||
detail::string_view_offset::from_sv(deser.value, buffer_first),
|
||||
is_blob
|
||||
);
|
||||
if (is_blob)
|
||||
{
|
||||
output = field_view(
|
||||
blob_view(reinterpret_cast<const unsigned char*>(deser.value.data()), deser.value.size())
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
output = field_view(deser.value);
|
||||
}
|
||||
return deserialize_errc::ok;
|
||||
}
|
||||
|
||||
@ -269,7 +274,6 @@ inline deserialize_errc deserialize_binary_field_time(
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_binary_field(
|
||||
deserialization_context& ctx,
|
||||
const metadata& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
field_view& output
|
||||
)
|
||||
{
|
||||
@ -299,13 +303,13 @@ inline boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_
|
||||
case column_type::enum_:
|
||||
case column_type::set:
|
||||
case column_type::decimal:
|
||||
case column_type::json: return deserialize_binary_field_string(ctx, output, buffer_first, false);
|
||||
case column_type::json: return deserialize_binary_field_string(ctx, output, false);
|
||||
// Blobs and anything else
|
||||
case column_type::binary:
|
||||
case column_type::varbinary:
|
||||
case column_type::blob:
|
||||
case column_type::geometry:
|
||||
default: return deserialize_binary_field_string(ctx, output, buffer_first, true);
|
||||
default: return deserialize_binary_field_string(ctx, output, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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_IMPL_DESERIALIZE_EXECUTE_RESPONSE_IPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_EXECUTE_RESPONSE_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/process_error_packet.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace detail {
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
inline boost::mysql::detail::execute_response boost::mysql::detail::deserialize_execute_response(
|
||||
boost::asio::const_buffer msg,
|
||||
capabilities caps,
|
||||
db_flavor flavor,
|
||||
diagnostics& diag
|
||||
) noexcept
|
||||
{
|
||||
// Response may be: ok_packet, err_packet, local infile request (not implemented)
|
||||
// If it is none of this, then the message type itself is the beginning of
|
||||
// a length-encoded int containing the field count
|
||||
deserialization_context ctx(msg, caps);
|
||||
std::uint8_t msg_type = 0;
|
||||
auto err = deserialize_message_part(ctx, msg_type);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (msg_type == ok_packet_header)
|
||||
{
|
||||
ok_packet ok_pack;
|
||||
err = deserialize_message(ctx, ok_pack);
|
||||
if (err)
|
||||
return err;
|
||||
return ok_pack;
|
||||
}
|
||||
else if (msg_type == error_packet_header)
|
||||
{
|
||||
return process_error_packet(ctx, flavor, diag);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resultset with metadata. First packet is an int_lenenc with
|
||||
// the number of field definitions to expect. Message type is part
|
||||
// of this packet, so we must rewind the context
|
||||
ctx.rewind(1);
|
||||
int_lenenc num_fields;
|
||||
err = deserialize_message(ctx, num_fields);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
// We should have at least one field.
|
||||
// The max number of fields is some value around 1024. For simplicity/extensibility,
|
||||
// we accept anything less than 0xffff
|
||||
if (num_fields.value == 0 || num_fields.value > 0xffffu)
|
||||
{
|
||||
return make_error_code(client_errc::protocol_value_error);
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(num_fields.value);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -8,6 +8,9 @@
|
||||
#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/detail/protocol/deserialize_binary_field.hpp>
|
||||
@ -30,22 +33,16 @@ inline bool is_next_field_null(const deserialization_context& ctx)
|
||||
|
||||
inline error_code deserialize_text_row(
|
||||
deserialization_context& ctx,
|
||||
const std::vector<metadata>& fields,
|
||||
const std::uint8_t* buffer_first,
|
||||
std::vector<field_view>& output
|
||||
metadata_collection_view meta,
|
||||
field_view* output
|
||||
)
|
||||
{
|
||||
// Make space
|
||||
std::size_t old_size = output.size();
|
||||
auto num_fields = fields.size();
|
||||
output.resize(old_size + num_fields);
|
||||
|
||||
for (std::vector<field_view>::size_type i = 0; i < num_fields; ++i)
|
||||
for (std::vector<field_view>::size_type i = 0; i < meta.size(); ++i)
|
||||
{
|
||||
if (is_next_field_null(ctx))
|
||||
{
|
||||
ctx.advance(1);
|
||||
output[old_size + i] = field_view(nullptr);
|
||||
output[i] = field_view(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -53,7 +50,7 @@ inline error_code deserialize_text_row(
|
||||
auto err = deserialize(ctx, value_str);
|
||||
if (err != deserialize_errc::ok)
|
||||
return to_error_code(err);
|
||||
err = deserialize_text_field(value_str.value, fields[i], buffer_first, output[old_size + i]);
|
||||
err = deserialize_text_field(value_str.value, meta[i], output[i]);
|
||||
if (err != deserialize_errc::ok)
|
||||
return to_error_code(err);
|
||||
}
|
||||
@ -65,9 +62,8 @@ inline error_code deserialize_text_row(
|
||||
|
||||
inline error_code deserialize_binary_row(
|
||||
deserialization_context& ctx,
|
||||
const std::vector<metadata>& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
std::vector<field_view>& output
|
||||
metadata_collection_view meta,
|
||||
field_view* output
|
||||
)
|
||||
{
|
||||
// Skip packet header (it is not part of the message in the binary
|
||||
@ -77,9 +73,7 @@ inline error_code deserialize_binary_row(
|
||||
ctx.advance(1);
|
||||
|
||||
// Number of fields
|
||||
std::size_t old_size = output.size();
|
||||
auto num_fields = meta.size();
|
||||
output.resize(old_size + num_fields);
|
||||
std::size_t num_fields = meta.size();
|
||||
|
||||
// Null bitmap
|
||||
null_bitmap_traits null_bitmap(binary_row_null_bitmap_offset, num_fields);
|
||||
@ -93,11 +87,11 @@ inline error_code deserialize_binary_row(
|
||||
{
|
||||
if (null_bitmap.is_null(null_bitmap_begin, i))
|
||||
{
|
||||
output[old_size + i] = field_view(nullptr);
|
||||
output[i] = field_view(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto err = deserialize_binary_field(ctx, meta[i], buffer_first, output[old_size + i]);
|
||||
auto err = deserialize_binary_field(ctx, meta[i], output[i]);
|
||||
if (err != deserialize_errc::ok)
|
||||
return to_error_code(err);
|
||||
}
|
||||
@ -117,29 +111,25 @@ inline error_code deserialize_binary_row(
|
||||
void boost::mysql::detail::deserialize_row(
|
||||
resultset_encoding encoding,
|
||||
deserialization_context& ctx,
|
||||
const std::vector<metadata>& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
std::vector<field_view>& output,
|
||||
metadata_collection_view meta,
|
||||
field_view* output,
|
||||
error_code& err
|
||||
)
|
||||
{
|
||||
err = encoding == detail::resultset_encoding::text
|
||||
? deserialize_text_row(ctx, meta, buffer_first, output)
|
||||
: deserialize_binary_row(ctx, meta, buffer_first, 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,
|
||||
const std::uint8_t* buffer_first, // to store strings as offsets and allow buffer reallocation
|
||||
execution_state& st,
|
||||
std::vector<field_view>& output,
|
||||
execution_state_impl& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
assert(!st.complete());
|
||||
assert(st.should_read_rows());
|
||||
|
||||
// Message type: row, error or eof?
|
||||
std::uint8_t msg_type = 0;
|
||||
@ -158,7 +148,7 @@ void boost::mysql::detail::deserialize_row(
|
||||
err = deserialize_message(ctx, ok_pack);
|
||||
if (err)
|
||||
return;
|
||||
execution_state_access::complete(st, ok_pack);
|
||||
st.on_row_ok_packet(ok_pack);
|
||||
}
|
||||
else if (msg_type == error_packet_header)
|
||||
{
|
||||
@ -169,14 +159,8 @@ void boost::mysql::detail::deserialize_row(
|
||||
{
|
||||
// An actual row
|
||||
ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message
|
||||
deserialize_row(
|
||||
execution_state_access::get_encoding(st),
|
||||
ctx,
|
||||
execution_state_access::get_metadata(st),
|
||||
buffer_first,
|
||||
output,
|
||||
err
|
||||
);
|
||||
field_view* storage = st.add_row();
|
||||
deserialize_row(st.encoding(), ctx, st.current_resultset_meta(), storage, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_TEXT_FIELD_IPP
|
||||
#define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_DESERIALIZE_TEXT_FIELD_IPP
|
||||
|
||||
#include <boost/mysql/blob_view.hpp>
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/datetime.hpp>
|
||||
@ -68,14 +69,15 @@ deserialize_errc deserialize_text_value_float(string_view from, field_view& to)
|
||||
}
|
||||
|
||||
// Strings
|
||||
inline deserialize_errc deserialize_text_value_string(
|
||||
string_view from,
|
||||
field_view& to,
|
||||
const std::uint8_t* buffer_first,
|
||||
bool is_blob
|
||||
) noexcept
|
||||
inline deserialize_errc deserialize_text_value_string(string_view from, field_view& to) noexcept
|
||||
{
|
||||
to = field_view_access::construct(string_view_offset::from_sv(from, buffer_first), is_blob);
|
||||
to = field_view(from);
|
||||
return deserialize_errc::ok;
|
||||
}
|
||||
|
||||
inline deserialize_errc deserialize_text_value_blob(string_view from, field_view& to) noexcept
|
||||
{
|
||||
to = field_view(blob_view(reinterpret_cast<const unsigned char*>(from.data()), from.size()));
|
||||
return deserialize_errc::ok;
|
||||
}
|
||||
|
||||
@ -269,7 +271,6 @@ inline deserialize_errc deserialize_text_value_time(
|
||||
inline boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_text_field(
|
||||
string_view from,
|
||||
const metadata& meta,
|
||||
const std::uint8_t* buffer_first,
|
||||
field_view& output
|
||||
)
|
||||
{
|
||||
@ -295,13 +296,13 @@ inline boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_
|
||||
case column_type::enum_:
|
||||
case column_type::set:
|
||||
case column_type::decimal:
|
||||
case column_type::json: return deserialize_text_value_string(from, output, buffer_first, false);
|
||||
case column_type::json: return deserialize_text_value_string(from, output);
|
||||
// Blobs and anything else
|
||||
case column_type::binary:
|
||||
case column_type::varbinary:
|
||||
case column_type::blob:
|
||||
case column_type::geometry:
|
||||
default: return deserialize_text_value_string(from, output, buffer_first, true);
|
||||
default: return deserialize_text_value_blob(from, output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#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 <cassert>
|
||||
@ -26,6 +27,12 @@ namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief Holds state for multi-function SQL execution operations.
|
||||
* \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
|
||||
* and \ref complete. They are mutually exclusive.
|
||||
* More states may be added in the future as more protocol features are implemented.
|
||||
*
|
||||
* \par Thread safety
|
||||
* Distinct objects: safe. \n
|
||||
* Shared objects: unsafe.
|
||||
@ -35,24 +42,101 @@ class execution_state
|
||||
public:
|
||||
/**
|
||||
* \brief Default constructor.
|
||||
* \details The constructed object is guaranteed to have `meta().empty()` and
|
||||
* `!complete()`.
|
||||
* \details The constructed object is guaranteed to have
|
||||
* `should_start_op() == true`.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
execution_state() = default;
|
||||
execution_state() noexcept : impl_(false){};
|
||||
|
||||
/**
|
||||
* \brief Copy constructor.
|
||||
* \par Exception safety
|
||||
* Strong guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `other`'s.
|
||||
*/
|
||||
execution_state(const execution_state& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Move constructor.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* Views obtained from `other` remain valid.
|
||||
*/
|
||||
execution_state(execution_state&& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Copy assignment.
|
||||
* \par Exception safety
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `other`'s. Views obtained from `*this`
|
||||
* are invalidated.
|
||||
*/
|
||||
execution_state& operator=(const execution_state& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Move assignment.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* Views obtained from `*this` are invalidated. Views obtained from `other` remain valid.
|
||||
*/
|
||||
execution_state& operator=(execution_state&& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Returns whether `*this` is in the initial state.
|
||||
* \details
|
||||
* Call `start_query`, `start_statement_execution` or their async countrparts to move
|
||||
* forward. No data is available in this state.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool should_start_op() const noexcept { return impl_.num_resultsets() == 0u; }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the next operation should be read resultset head.
|
||||
* \details
|
||||
* Call \ref connection::read_resultset_head or its async counterpart to move forward.
|
||||
* Metadata and OK data for the previous resultset is available in this state.
|
||||
*
|
||||
* \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());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns whether the next operation should be read some rows.
|
||||
* \details
|
||||
* Call \ref connection::read_some_rows or its async counterpart to move forward.
|
||||
* Metadata for the current resultset is available in this state.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool should_read_rows() const noexcept { return impl_.should_read_rows(); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether all the messages generated by this operation have been read.
|
||||
* \details
|
||||
* Once `complete`, you may access extra information about the operation, like
|
||||
* \ref affected_rows or \ref last_insert_id.
|
||||
* No further network calls are required to move forward. Metadata and OK data for the last
|
||||
* resultset are available in this state.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool complete() const noexcept { return eof_received_; }
|
||||
bool complete() const noexcept { return impl_.complete(); }
|
||||
|
||||
/**
|
||||
* \brief Returns metadata about the columns in the query.
|
||||
@ -70,53 +154,41 @@ public:
|
||||
*/
|
||||
metadata_collection_view meta() const noexcept
|
||||
{
|
||||
return metadata_collection_view(meta_.data(), meta_.size());
|
||||
return impl_.num_resultsets() == 0u ? metadata_collection_view() : impl_.get_meta(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of rows affected by the executed SQL statement.
|
||||
* \brief Returns the number of rows affected by the SQL statement associated to this resultset.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->complete() == true`
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
std::uint64_t affected_rows() const noexcept
|
||||
{
|
||||
assert(complete());
|
||||
return affected_rows_;
|
||||
}
|
||||
std::uint64_t affected_rows() const noexcept { return impl_.get_affected_rows(0); }
|
||||
|
||||
/**
|
||||
* \brief Returns the last insert ID produced by the executed SQL statement.
|
||||
* \brief Returns the last insert ID produced by the SQL statement associated to this resultset.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->complete() == true`
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
std::uint64_t last_insert_id() const noexcept
|
||||
{
|
||||
assert(complete());
|
||||
return last_insert_id_;
|
||||
}
|
||||
std::uint64_t last_insert_id() const noexcept { return impl_.get_last_insert_id(0); }
|
||||
|
||||
/**
|
||||
* \brief Returns the number of warnings produced by the executed SQL statement.
|
||||
* \brief Returns the number of warnings produced by the SQL statement associated to this resultset.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->complete() == true`
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*/
|
||||
unsigned warning_count() const noexcept
|
||||
{
|
||||
assert(complete());
|
||||
return warnings_;
|
||||
}
|
||||
unsigned warning_count() const noexcept { return impl_.get_warning_count(0); }
|
||||
|
||||
/**
|
||||
* \brief Returns additionat text information about the execution of the SQL statement.
|
||||
* \brief Returns additionat 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>.
|
||||
@ -127,37 +199,36 @@ public:
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->complete() == true`
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* This function returns a view object, with reference semantics. The returned view points into
|
||||
* 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
|
||||
{
|
||||
assert(complete());
|
||||
return string_view(info_.data(), info_.size());
|
||||
}
|
||||
string_view info() const noexcept { return impl_.get_info(0); }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the current resultset represents a procedure OUT params.
|
||||
* \par Preconditions
|
||||
* `this->complete() == true || this->should_read_head() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool is_out_params() const noexcept { return impl_.get_is_out_params(0); }
|
||||
|
||||
private:
|
||||
bool eof_received_{false};
|
||||
std::uint8_t seqnum_{};
|
||||
detail::resultset_encoding encoding_{detail::resultset_encoding::text};
|
||||
std::vector<metadata> meta_;
|
||||
std::uint64_t affected_rows_{};
|
||||
std::uint64_t last_insert_id_{};
|
||||
std::uint16_t warnings_{};
|
||||
std::vector<char> info_; // guarantee that no SBO is used
|
||||
detail::execution_state_impl impl_;
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend struct detail::execution_state_access;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include <boost/mysql/impl/execution_state.hpp>
|
||||
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/impl/execution_state.hpp>
|
||||
|
||||
#endif
|
||||
|
@ -30,6 +30,7 @@ class handshake_params
|
||||
string_view database_;
|
||||
std::uint16_t connection_collation_;
|
||||
ssl_mode ssl_;
|
||||
bool multi_queries_;
|
||||
|
||||
public:
|
||||
/// The default collation to use with the connection (`utf8mb4_general_ci` on both MySQL and MariaDB).
|
||||
@ -48,19 +49,23 @@ public:
|
||||
* `utf8mb4_general_ci` (see \ref default_collation), which is compatible with MySQL 5.x, 8.x and MariaDB.
|
||||
* \param mode The \ref ssl_mode to use with this connection; ignored if
|
||||
* the connection's `Stream` does not support SSL.
|
||||
* \param multi_queries Whether to enable support for executing semicolon-separated
|
||||
* queries using \ref connection::query. Disabled by default.
|
||||
*/
|
||||
handshake_params(
|
||||
string_view username,
|
||||
string_view password,
|
||||
string_view db = "",
|
||||
std::uint16_t connection_col = default_collation,
|
||||
ssl_mode mode = ssl_mode::require
|
||||
ssl_mode mode = ssl_mode::require,
|
||||
bool multi_queries = false
|
||||
)
|
||||
: username_(username),
|
||||
password_(password),
|
||||
database_(db),
|
||||
connection_collation_(connection_col),
|
||||
ssl_(mode)
|
||||
ssl_(mode),
|
||||
multi_queries_(multi_queries)
|
||||
{
|
||||
}
|
||||
|
||||
@ -133,6 +138,20 @@ public:
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
void set_ssl(ssl_mode value) noexcept { ssl_ = value; }
|
||||
|
||||
/**
|
||||
* \brief Retrieves whether multi-query support is enabled.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
bool multi_queries() const noexcept { return multi_queries_; }
|
||||
|
||||
/**
|
||||
* \brief Enables or disables support for the multi-query feature.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
void set_multi_queries(bool v) noexcept { multi_queries_ = v; }
|
||||
};
|
||||
|
||||
} // namespace mysql
|
||||
|
@ -21,15 +21,13 @@
|
||||
#include <boost/mysql/detail/network_algorithms/close_connection.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/close_statement.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/connect.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/execute_statement.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/handshake.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/high_level_execution.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/ping.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/prepare_statement.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/query.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/start_query.hpp>
|
||||
#include <boost/mysql/detail/network_algorithms/start_statement_execution.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/assert/source_location.hpp>
|
||||
@ -416,6 +414,53 @@ boost::mysql::connection<Stream>::async_close_statement(
|
||||
return detail::async_close_statement(get_channel(), stmt, diag, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
// read resultset head
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::read_resultset_head(
|
||||
execution_state& st,
|
||||
error_code& err,
|
||||
diagnostics& diag
|
||||
)
|
||||
{
|
||||
detail::clear_errors(err, diag);
|
||||
return detail::read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
err,
|
||||
diag
|
||||
);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
void boost::mysql::connection<Stream>::read_resultset_head(execution_state& st)
|
||||
{
|
||||
detail::error_block blk;
|
||||
detail::read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
blk.err,
|
||||
blk.diag
|
||||
);
|
||||
blk.check(BOOST_CURRENT_LOCATION);
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <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,
|
||||
diagnostics& diag,
|
||||
CompletionToken&& token
|
||||
)
|
||||
{
|
||||
return detail::async_read_resultset_head(
|
||||
get_channel(),
|
||||
detail::execution_state_access::get_impl(st),
|
||||
diag,
|
||||
std::forward<CompletionToken>(token)
|
||||
);
|
||||
}
|
||||
|
||||
// read some rows
|
||||
template <class Stream>
|
||||
boost::mysql::rows_view boost::mysql::connection<Stream>::read_some_rows(
|
||||
|
@ -11,46 +11,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
struct boost::mysql::detail::execution_state_access
|
||||
{
|
||||
static void reset(execution_state& st, detail::resultset_encoding encoding) noexcept
|
||||
{
|
||||
st.seqnum_ = 0;
|
||||
st.encoding_ = encoding;
|
||||
st.meta_.clear();
|
||||
st.eof_received_ = false;
|
||||
}
|
||||
|
||||
static void complete(execution_state& st, const detail::ok_packet& pack)
|
||||
{
|
||||
st.affected_rows_ = pack.affected_rows.value;
|
||||
st.last_insert_id_ = pack.last_insert_id.value;
|
||||
st.warnings_ = pack.warnings;
|
||||
st.info_.assign(pack.info.value.begin(), pack.info.value.end());
|
||||
st.eof_received_ = true;
|
||||
}
|
||||
|
||||
static void prepare_meta(execution_state& st, std::size_t num_fields) { st.meta_.reserve(num_fields); }
|
||||
|
||||
static void add_meta(
|
||||
execution_state& st,
|
||||
const detail::column_definition_packet& pack,
|
||||
metadata_mode mode
|
||||
)
|
||||
{
|
||||
st.meta_.push_back(metadata_access::construct(pack, mode == metadata_mode::full));
|
||||
}
|
||||
|
||||
static resultset_encoding get_encoding(const execution_state& st) noexcept { return st.encoding_; }
|
||||
|
||||
static std::uint8_t& get_sequence_number(execution_state& st) noexcept { return st.seqnum_; }
|
||||
|
||||
static std::vector<metadata>& get_metadata(execution_state& st) noexcept { return st.meta_; }
|
||||
static detail::execution_state_impl& get_impl(execution_state& st) noexcept { return st.impl_; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -8,15 +8,15 @@
|
||||
#ifndef BOOST_MYSQL_IMPL_RESULTS_HPP
|
||||
#define BOOST_MYSQL_IMPL_RESULTS_HPP
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.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& get_state(results& result) noexcept { return result.st_; }
|
||||
static rows& get_rows(results& result) noexcept { return result.rows_; }
|
||||
static execution_state_impl& get_impl(results& result) noexcept { return result.impl_; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
25
include/boost/mysql/impl/resultset_view.hpp
Normal file
25
include/boost/mysql/impl/resultset_view.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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_RESULTSET_VIEW_HPP
|
||||
#define BOOST_MYSQL_IMPL_RESULTSET_VIEW_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
struct boost::mysql::detail::resultset_view_access
|
||||
{
|
||||
static resultset_view construct(const detail::execution_state_impl& st, std::size_t index)
|
||||
{
|
||||
return resultset_view(st, index);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -22,15 +22,7 @@
|
||||
|
||||
boost::mysql::rows& boost::mysql::rows::operator=(const rows_view& rhs)
|
||||
{
|
||||
// Protect against self-assignment
|
||||
if (rhs.fields_ == fields_.data())
|
||||
{
|
||||
assert(rhs.num_fields_ == fields_.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
assign(rhs.fields_, rhs.num_fields_);
|
||||
}
|
||||
impl_.assign(rhs.fields_, rhs.num_fields_);
|
||||
num_columns_ = rhs.num_columns_;
|
||||
return *this;
|
||||
}
|
||||
@ -41,18 +33,13 @@ boost::mysql::row_view boost::mysql::rows::at(std::size_t i) const
|
||||
{
|
||||
throw std::out_of_range("rows::at");
|
||||
}
|
||||
return detail::row_slice(fields_.data(), num_columns_, i);
|
||||
return detail::row_slice(impl_.fields().data(), num_columns_, i);
|
||||
}
|
||||
|
||||
boost::mysql::row_view boost::mysql::rows::operator[](std::size_t i) const noexcept
|
||||
{
|
||||
assert(i < size());
|
||||
return detail::row_slice(fields_.data(), num_columns_, i);
|
||||
return detail::row_slice(impl_.fields().data(), num_columns_, i);
|
||||
}
|
||||
|
||||
struct boost::mysql::detail::rows_access
|
||||
{
|
||||
static void clear(rows& r) noexcept { r.clear(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -10,20 +10,28 @@
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/resultset.hpp>
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/results_iterator.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief Holds the results of a SQL query.
|
||||
* \details The results (rows, metadata and additional info) are held in-memory.
|
||||
* \details
|
||||
* This object can store the results of single and multi resultset queries.
|
||||
* For the former, you use \ref meta, \ref rows, \ref affected_rows and so on.
|
||||
* For the latter, this class is a random-access collection of \ref resultset objects.
|
||||
* \n
|
||||
* \par Thread safety
|
||||
* Distinct objects: safe. \n
|
||||
@ -32,6 +40,34 @@ namespace mysql {
|
||||
class results
|
||||
{
|
||||
public:
|
||||
#ifdef BOOST_MYSQL_DOXYGEN
|
||||
/**
|
||||
* \brief A random access iterator to an element.
|
||||
* \details The exact type of the iterator is unspecified.
|
||||
*/
|
||||
using iterator = __see_below__;
|
||||
#else
|
||||
using iterator = detail::results_iterator;
|
||||
#endif
|
||||
|
||||
/// \copydoc iterator
|
||||
using const_iterator = iterator;
|
||||
|
||||
/// A type that can hold elements in this collection with value semantics.
|
||||
using value_type = resultset;
|
||||
|
||||
/// The reference type.
|
||||
using reference = resultset_view;
|
||||
|
||||
/// \copydoc reference
|
||||
using const_reference = resultset_view;
|
||||
|
||||
/// An unsigned integer type to represent sizes.
|
||||
using size_type = std::size_t;
|
||||
|
||||
/// A signed integer type used to represent differences.
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* \brief Default constructor.
|
||||
* \details Constructs an empty results object, with `this->has_value() == false`.
|
||||
@ -39,7 +75,7 @@ public:
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
results() = default;
|
||||
results() noexcept : impl_(true) {}
|
||||
|
||||
/**
|
||||
* \brief Copy constructor.
|
||||
@ -54,7 +90,8 @@ public:
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* View objects obtained from `other` remain valid.
|
||||
* View objects obtained from `other` using \ref rows and \ref meta remain valid.
|
||||
* Any other views and iterators referencing `other` are invalidated.
|
||||
*/
|
||||
results(results&& other) = default;
|
||||
|
||||
@ -64,7 +101,7 @@ public:
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* View objects referencing `*this` are invalidated.
|
||||
* Views and iterators referencing `*this` are invalidated.
|
||||
*/
|
||||
results& operator=(const results& other) = default;
|
||||
|
||||
@ -74,7 +111,8 @@ public:
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* View objects referencing `other` remain valid. View objects
|
||||
* View objects obtained from `other` using \ref rows and \ref meta remain valid.
|
||||
* Any other views and iterators referencing `other` are invalidated. Views and iterators
|
||||
* referencing `*this` are invalidated.
|
||||
*/
|
||||
results& operator=(results&& other) = default;
|
||||
@ -90,11 +128,18 @@ public:
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool has_value() const noexcept { return st_.complete(); }
|
||||
bool has_value() const noexcept { return impl_.complete(); }
|
||||
|
||||
/**
|
||||
* \brief Returns the rows retrieved by the SQL query.
|
||||
* \details
|
||||
* For operations returning more than one resultset, returns the rows
|
||||
* for the first resultset.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
@ -105,11 +150,14 @@ public:
|
||||
* This function returns a view object, with reference semantics. The returned view points into
|
||||
* memory owned by `*this`, and will be valid as long as `*this` or an object move-constructed
|
||||
* from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
rows_view rows() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return rows_;
|
||||
return impl_.get_rows(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,6 +165,9 @@ public:
|
||||
* \details
|
||||
* The returned collection will have as many \ref metadata objects as columns retrieved by
|
||||
* the SQL query, and in the same order.
|
||||
* \n
|
||||
* For operations returning more than one resultset, returns metadata
|
||||
* for the first resultset.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
@ -128,53 +179,77 @@ public:
|
||||
* This function returns a view object, with reference semantics. The returned view points into
|
||||
* memory owned by `*this`, and will be valid as long as `*this` or an object move-constructed
|
||||
* from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
metadata_collection_view meta() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_.meta();
|
||||
return impl_.get_meta(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of rows affected by the executed SQL statement.
|
||||
* \details
|
||||
* For operations returning more than one resultset, returns the
|
||||
* first resultset's affected rows.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t affected_rows() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_.affected_rows();
|
||||
return impl_.get_affected_rows(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last insert ID produced by the executed SQL statement.
|
||||
* \details
|
||||
* For operations returning more than one resultset, returns the
|
||||
* first resultset's last insert ID.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t last_insert_id() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_.last_insert_id();
|
||||
return impl_.get_last_insert_id(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of warnings produced by the executed SQL statement.
|
||||
* \details
|
||||
* For operations returning more than one resultset, returns the
|
||||
* first resultset's warning count.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
unsigned warning_count() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_.warning_count();
|
||||
return impl_.get_warning_count(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,6 +259,9 @@ public:
|
||||
* href="https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html">here</a>.
|
||||
* \n
|
||||
* The returned string always uses ASCII encoding, regardless of the connection's character set.
|
||||
* \n
|
||||
* For operations returning more than one resultset, returns the
|
||||
* first resultset's info.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
@ -195,17 +273,206 @@ public:
|
||||
* This function returns a view object, with reference semantics. The returned view points into
|
||||
* memory owned by `*this`, and will be valid as long as `*this` or an object move-constructed
|
||||
* from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
string_view info() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_.info();
|
||||
return impl_.get_info(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns an iterator pointing to the first resultset that this object contains.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned iterator and any reference obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate iterators.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
iterator begin() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return iterator(&impl_, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns an iterator pointing to one-past-the-last resultset that this object contains.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned iterator and any reference obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate iterators.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
iterator end() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return iterator(&impl_, size());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the i-th resultset or throws an exception.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* Strong guranatee. Throws on invalid input.
|
||||
* \throws std::out_of_range `i >= this->size()`
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate references.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
inline resultset_view at(std::size_t i) const
|
||||
{
|
||||
assert(has_value());
|
||||
if (i >= size())
|
||||
throw std::out_of_range("results::at: out of range");
|
||||
return detail::resultset_view_access::construct(impl_, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the i-th resultset (unchecked access).
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true && i < this->size()`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate references.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
resultset_view operator[](std::size_t i) const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
assert(i < size());
|
||||
return detail::resultset_view_access::construct(impl_, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the first resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate references.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
resultset_view front() const noexcept { return (*this)[0]; }
|
||||
|
||||
/**
|
||||
* \brief Returns the last resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate references.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
resultset_view back() const noexcept { return (*this)[size() - 1]; }
|
||||
|
||||
/**
|
||||
* \brief Returns whether the collection contains any resultset.
|
||||
* \details
|
||||
* This function is provided for compatibility with standard collections,
|
||||
* and always returns false, since any valid `results` contains at least one resultset.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool empty() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of resultsets that this collection contains.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return impl_.num_resultsets();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the output parameters of a stored procedure call.
|
||||
* \details
|
||||
* Relevant for `CALL` operations performed using prepared statements that
|
||||
* bind placeholders to `OUT` or `INOUT` parameters. Returns a row containing a field per
|
||||
* bound output parameter.
|
||||
* \n
|
||||
* If this operation had no output parameters (e.g. it wasn't a `CALL`), returns an empty row.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` is alive. Move operations invalidate references.
|
||||
*
|
||||
* \par Complexity
|
||||
* Linear on `this->size()`.
|
||||
*/
|
||||
row_view out_params() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return impl_.get_out_params();
|
||||
}
|
||||
|
||||
private:
|
||||
execution_state st_;
|
||||
::boost::mysql::rows rows_;
|
||||
|
||||
detail::execution_state_impl impl_;
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend struct detail::results_access;
|
||||
#endif
|
||||
|
320
include/boost/mysql/resultset.hpp
Normal file
320
include/boost/mysql/resultset.hpp
Normal file
@ -0,0 +1,320 @@
|
||||
//
|
||||
// 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_RESULTSET_HPP
|
||||
#define BOOST_MYSQL_RESULTSET_HPP
|
||||
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/resultset_view.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief An owning resultset, containing metadata, rows and additional info.
|
||||
* \details
|
||||
* Similar to \ref results, but can only represent a single resultset (while `results` can hold
|
||||
* multiple resultsets). Can be used to take ownership of a \ref resultset_view.
|
||||
*/
|
||||
class resultset
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs an empty resultset.
|
||||
* \details
|
||||
* The constructed object has `this->has_value() == false`.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
resultset() = default;
|
||||
|
||||
/**
|
||||
* \brief Copy constructor.
|
||||
* \par Exception safety
|
||||
* Strong guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `other`'s.
|
||||
*
|
||||
* \par Complexity
|
||||
* Linear on rows and metadata size for `other`.
|
||||
*/
|
||||
resultset(const resultset& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Move constructor.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* Views obtained from `other` remain valid.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
resultset(resultset&& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Copy assignment.
|
||||
* \par Exception safety
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `other`'s. Views obtained from `*this`
|
||||
* are invalidated.
|
||||
*
|
||||
* \par Complexity
|
||||
* Linear on rows and metadata size for `other`.
|
||||
*/
|
||||
resultset& operator=(const resultset& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Move assignment.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* Views obtained from `*this` are invalidated. Views obtained from `other` remain valid.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
resultset& operator=(resultset&& other) = default;
|
||||
|
||||
/**
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~resultset() = default;
|
||||
|
||||
/**
|
||||
* \brief Constructs a resultset object by taking ownership of a view.
|
||||
* \par Exception safety
|
||||
* Strong guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `v`'s (the contents of `v` will be copied
|
||||
* into `*this`).
|
||||
*
|
||||
* \par Complexity
|
||||
* Linear on rows and metadata size for `v`.
|
||||
*/
|
||||
resultset(resultset_view v) { assign(v); }
|
||||
|
||||
/**
|
||||
* \brief Replaces the contents of `*this` with a \ref resultset_view.
|
||||
* \par Exception safety
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* `*this` lifetime will be independent of `v`'s (the contents of `v` will be copied
|
||||
* into `*this`). Views obtained from `*this` are invalidated.
|
||||
*
|
||||
* \par Complexity
|
||||
* Linear on rows and metadata size for `v` and `*this`.
|
||||
*/
|
||||
resultset& operator=(resultset_view v)
|
||||
{
|
||||
assign(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns whether this object contains actual data or not.
|
||||
* \details
|
||||
* Only returns true for default-constructed objects.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool has_value() const noexcept { return has_value_; }
|
||||
|
||||
/**
|
||||
* \brief Returns the rows that this resultset contains.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` or an object move-constructed from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
rows_view rows() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return rws_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns metadata for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` or an object move-constructed from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
metadata_collection_view meta() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return meta_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of affected rows for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t affected_rows() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return affected_rows_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last insert ID for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t last_insert_id() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return last_insert_id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of warnings for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
unsigned warning_count() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return warnings_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns additional information for 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>.
|
||||
* \n
|
||||
* The returned string always uses ASCII encoding, regardless of the connection's character set.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* `*this` or an object move-constructed from `*this` are alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
string_view info() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return string_view(info_.data(), info_.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns whether this resultset represents a procedure OUT params.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool is_out_params() const noexcept
|
||||
{
|
||||
assert(has_value_);
|
||||
return is_out_params_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_value_{false};
|
||||
std::vector<metadata> meta_;
|
||||
::boost::mysql::rows rws_;
|
||||
std::uint64_t affected_rows_{};
|
||||
std::uint64_t last_insert_id_{};
|
||||
std::uint16_t warnings_{};
|
||||
std::vector<char> info_;
|
||||
bool is_out_params_{false};
|
||||
|
||||
void assign(resultset_view v)
|
||||
{
|
||||
has_value_ = v.has_value();
|
||||
if (has_value_)
|
||||
{
|
||||
meta_.assign(v.meta().begin(), v.meta().end());
|
||||
rws_ = v.rows();
|
||||
affected_rows_ = v.affected_rows();
|
||||
last_insert_id_ = v.last_insert_id();
|
||||
warnings_ = v.warning_count();
|
||||
info_.assign(v.info().begin(), v.info().end());
|
||||
is_out_params_ = v.is_out_params();
|
||||
}
|
||||
else
|
||||
{
|
||||
meta_.clear();
|
||||
rws_ = ::boost::mysql::rows();
|
||||
affected_rows_ = 0;
|
||||
last_insert_id_ = 0;
|
||||
warnings_ = 0;
|
||||
info_.clear();
|
||||
is_out_params_ = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
207
include/boost/mysql/resultset_view.hpp
Normal file
207
include/boost/mysql/resultset_view.hpp
Normal file
@ -0,0 +1,207 @@
|
||||
//
|
||||
// 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_RESULTSET_VIEW_HPP
|
||||
#define BOOST_MYSQL_RESULTSET_VIEW_HPP
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
|
||||
/**
|
||||
* \brief A non-owning reference to a resultset.
|
||||
* \details
|
||||
* A `resultset_view` points to memory owned by an external object, usually a \ref results.
|
||||
* The view and any other reference type obtained from it are valid as long as the
|
||||
* object they point to is alive.
|
||||
*/
|
||||
class resultset_view
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Constructs a view with `this->has_value() == false`.
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
resultset_view() = default;
|
||||
|
||||
/**
|
||||
* \brief Returns whether this is a null view or not.
|
||||
* \details
|
||||
* Only returns true for default-constructed views.
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool has_value() const noexcept { return st_ != nullptr; }
|
||||
|
||||
/**
|
||||
* \brief Returns the rows for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* the object that `*this` points to is alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
rows_view rows() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_rows(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns metadata for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* the object that `*this` points to is alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
metadata_collection_view meta() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_meta(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of affected rows for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t affected_rows() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_affected_rows(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last insert ID for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
std::uint64_t last_insert_id() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_last_insert_id(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the number of warnings for this resultset.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
unsigned warning_count() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_warning_count(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns additional information for 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>.
|
||||
* \n
|
||||
* The returned string always uses ASCII encoding, regardless of the connection's character set.
|
||||
*
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Object lifetimes
|
||||
* The returned reference and any other references obtained from it are valid as long as
|
||||
* the object that `*this` points to is alive.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
string_view info() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_info(index_);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns whether this resultset represents a procedure OUT params.
|
||||
* \par Preconditions
|
||||
* `this->has_value() == true`
|
||||
*
|
||||
* \par Exception safety
|
||||
* No-throw guarantee.
|
||||
*
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
bool is_out_params() const noexcept
|
||||
{
|
||||
assert(has_value());
|
||||
return st_->get_is_out_params(index_);
|
||||
}
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
const resultset_view* operator->() const noexcept { return this; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
const detail::execution_state_impl* st_{};
|
||||
std::size_t index_{};
|
||||
|
||||
resultset_view(const detail::execution_state_impl& st, std::size_t index) noexcept
|
||||
: st_(&st), index_(index)
|
||||
{
|
||||
}
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend struct detail::resultset_view_access;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#include <boost/mysql/impl/resultset_view.hpp>
|
||||
|
||||
#endif
|
@ -13,7 +13,7 @@
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/row_base.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
@ -35,8 +35,10 @@ namespace mysql {
|
||||
* `row`'s internal storage. These views behave like references, and are valid as long as pointers,
|
||||
* iterators and references into the `row` remain valid.
|
||||
*/
|
||||
class row : private detail::row_base
|
||||
class row
|
||||
{
|
||||
detail::row_impl impl_;
|
||||
|
||||
public:
|
||||
#ifdef BOOST_MYSQL_DOXYGEN
|
||||
/**
|
||||
@ -146,7 +148,7 @@ public:
|
||||
* \par Complexity
|
||||
* Linear on `r.size()`.
|
||||
*/
|
||||
row(row_view r) : detail::row_base(r.begin(), r.size()) {}
|
||||
row(row_view r) : impl_(r.begin(), r.size()) {}
|
||||
|
||||
/**
|
||||
* \brief Replaces the contents with a \ref row_view.
|
||||
@ -163,42 +165,33 @@ public:
|
||||
*/
|
||||
row& operator=(row_view r)
|
||||
{
|
||||
// Protect against self-assignment. This is valid as long as we
|
||||
// don't implement sub-range operators (e.g. row_view[2:4])
|
||||
if (r.fields_ == fields_.data())
|
||||
{
|
||||
assert(r.size() == fields_.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
assign(r.begin(), r.size());
|
||||
}
|
||||
impl_.assign(r.begin(), r.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// \copydoc row_view::begin
|
||||
const_iterator begin() const noexcept { return fields_.data(); }
|
||||
const_iterator begin() const noexcept { return impl_.fields().data(); }
|
||||
|
||||
/// \copydoc row_view::end
|
||||
const_iterator end() const noexcept { return fields_.data() + fields_.size(); }
|
||||
const_iterator end() const noexcept { return impl_.fields().data() + impl_.fields().size(); }
|
||||
|
||||
/// \copydoc row_view::at
|
||||
field_view at(std::size_t i) const { return fields_.at(i); }
|
||||
field_view at(std::size_t i) const { return impl_.fields().at(i); }
|
||||
|
||||
/// \copydoc row_view::operator[]
|
||||
field_view operator[](std::size_t i) const noexcept { return fields_[i]; }
|
||||
field_view operator[](std::size_t i) const noexcept { return impl_.fields()[i]; }
|
||||
|
||||
/// \copydoc row_view::front
|
||||
field_view front() const noexcept { return fields_.front(); }
|
||||
field_view front() const noexcept { return impl_.fields().front(); }
|
||||
|
||||
/// \copydoc row_view::back
|
||||
field_view back() const noexcept { return fields_.back(); }
|
||||
field_view back() const noexcept { return impl_.fields().back(); }
|
||||
|
||||
/// \copydoc row_view::empty
|
||||
bool empty() const noexcept { return fields_.empty(); }
|
||||
bool empty() const noexcept { return impl_.fields().empty(); }
|
||||
|
||||
/// \copydoc row_view::size
|
||||
std::size_t size() const noexcept { return fields_.size(); }
|
||||
std::size_t size() const noexcept { return impl_.fields().size(); }
|
||||
|
||||
/**
|
||||
* \brief Creates a \ref row_view that references `*this`.
|
||||
@ -212,7 +205,7 @@ public:
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
operator row_view() const noexcept { return row_view(fields_.data(), fields_.size()); }
|
||||
operator row_view() const noexcept { return row_view(impl_.fields().data(), impl_.fields().size()); }
|
||||
|
||||
/// \copydoc row_view::as_vector
|
||||
template <class Allocator>
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <boost/mysql/rows_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/row_base.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/row_impl.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
|
||||
|
||||
namespace boost {
|
||||
@ -35,7 +35,7 @@ namespace mysql {
|
||||
* \n
|
||||
* Although owning, `rows` is read-only. It's optimized for memory re-use.
|
||||
*/
|
||||
class rows : private detail::row_base
|
||||
class rows
|
||||
{
|
||||
public:
|
||||
#ifdef BOOST_MYSQL_DOXYGEN
|
||||
@ -151,10 +151,10 @@ public:
|
||||
* \par Complexity
|
||||
* Linear on `r.size() * r.num_columns()`.
|
||||
*/
|
||||
rows(const rows_view& r) : detail::row_base(r.fields_, r.num_fields_), num_columns_(r.num_columns_) {}
|
||||
rows(const rows_view& r) : impl_(r.fields_, r.num_fields_), num_columns_(r.num_columns_) {}
|
||||
|
||||
/**
|
||||
* \brief Replaces the contents with a \ref rows_view.
|
||||
* \brief Replaces the contents of `*this` with a \ref rows_view.
|
||||
* \par Exception safety
|
||||
* Basic guarantee. Internal allocations may throw.
|
||||
*
|
||||
@ -169,10 +169,10 @@ public:
|
||||
inline rows& operator=(const rows_view& r);
|
||||
|
||||
/// \copydoc rows_view::begin
|
||||
iterator begin() const noexcept { return iterator(fields_.data(), num_columns_, 0); }
|
||||
iterator begin() const noexcept { return iterator(impl_.fields().data(), num_columns_, 0); }
|
||||
|
||||
/// \copydoc rows_view::end
|
||||
iterator end() const noexcept { return iterator(fields_.data(), num_columns_, size()); }
|
||||
iterator end() const noexcept { return iterator(impl_.fields().data(), num_columns_, size()); }
|
||||
|
||||
/// \copydoc rows_view::at
|
||||
inline row_view at(std::size_t i) const;
|
||||
@ -187,10 +187,10 @@ public:
|
||||
row_view back() const noexcept { return (*this)[size() - 1]; }
|
||||
|
||||
/// \copydoc rows_view::empty
|
||||
bool empty() const noexcept { return fields_.empty(); }
|
||||
bool empty() const noexcept { return impl_.fields().empty(); }
|
||||
|
||||
/// \copydoc rows_view::size
|
||||
std::size_t size() const noexcept { return num_columns_ == 0 ? 0 : fields_.size() / num_columns_; }
|
||||
std::size_t size() const noexcept { return num_columns_ == 0 ? 0 : impl_.fields().size() / num_columns_; }
|
||||
|
||||
/// \copydoc rows_view::num_columns
|
||||
std::size_t num_columns() const noexcept { return num_columns_; }
|
||||
@ -207,14 +207,14 @@ public:
|
||||
* \par Complexity
|
||||
* Constant.
|
||||
*/
|
||||
operator rows_view() const noexcept { return rows_view(fields_.data(), fields_.size(), num_columns_); }
|
||||
operator rows_view() const noexcept
|
||||
{
|
||||
return rows_view(impl_.fields().data(), impl_.fields().size(), num_columns_);
|
||||
}
|
||||
|
||||
private:
|
||||
detail::row_impl impl_;
|
||||
std::size_t num_columns_{};
|
||||
|
||||
#ifndef BOOST_MYSQL_DOXYGEN
|
||||
friend struct detail::rows_access;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
51
test/common/check_meta.hpp
Normal file
51
test/common/check_meta.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// 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_TEST_COMMON_CHECK_META_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CHECK_META_HPP
|
||||
|
||||
// This is a lighter check than integ tests' metadata_validator
|
||||
|
||||
#include <boost/mysql/column_type.hpp>
|
||||
#include <boost/mysql/metadata_collection_view.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
inline void check_meta(metadata_collection_view meta, const std::vector<column_type>& expected_types)
|
||||
{
|
||||
BOOST_TEST_REQUIRE(meta.size() == expected_types.size());
|
||||
for (std::size_t i = 0; i < meta.size(); ++i)
|
||||
{
|
||||
BOOST_TEST(meta[i].type() == expected_types[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline void check_meta(
|
||||
metadata_collection_view meta,
|
||||
const std::vector<std::pair<column_type, string_view>>& expected
|
||||
)
|
||||
{
|
||||
BOOST_TEST_REQUIRE(meta.size() == expected.size());
|
||||
for (std::size_t i = 0; i < meta.size(); ++i)
|
||||
{
|
||||
BOOST_TEST(meta[i].type() == expected[i].first);
|
||||
BOOST_TEST(meta[i].type() == expected[i].first);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -1,45 +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_TEST_COMMON_CREATE_EXECUTION_STATE_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATE_EXECUTION_STATE_HPP
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
inline execution_state create_execution_state(
|
||||
detail::resultset_encoding enc,
|
||||
const std::vector<detail::protocol_field_type>& types,
|
||||
std::uint8_t seqnum = 0
|
||||
)
|
||||
{
|
||||
execution_state res;
|
||||
detail::execution_state_access::reset(res, enc);
|
||||
boost::mysql::detail::column_definition_packet coldef{};
|
||||
for (auto type : types)
|
||||
{
|
||||
coldef.type = type;
|
||||
coldef.flags = 0;
|
||||
coldef.decimals = 0;
|
||||
detail::execution_state_access::add_meta(res, coldef, metadata_mode::minimal);
|
||||
}
|
||||
detail::execution_state_access::get_sequence_number(res) = seqnum;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -1,48 +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_TEST_COMMON_CREATE_META_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATE_META_HPP
|
||||
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/mysql_collations.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
inline metadata create_meta(const detail::column_definition_packet& coldef, bool copy_strings)
|
||||
{
|
||||
return detail::metadata_access::construct(coldef, copy_strings);
|
||||
}
|
||||
|
||||
inline metadata create_meta(
|
||||
detail::protocol_field_type type,
|
||||
std::uint16_t flags = 0,
|
||||
std::uint8_t decimals = 0,
|
||||
std::uint16_t collation = mysql_collations::utf8mb4_general_ci
|
||||
)
|
||||
{
|
||||
detail::column_definition_packet coldef{};
|
||||
coldef.type = type;
|
||||
coldef.flags = flags;
|
||||
coldef.decimals = decimals;
|
||||
coldef.character_set = collation;
|
||||
return create_meta(coldef, true);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -1,35 +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_TEST_COMMON_CREATE_STATEMENT_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATE_STATEMENT_HPP
|
||||
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
inline statement create_statement(std::uint16_t num_params, std::uint32_t stmt_id = 1)
|
||||
{
|
||||
statement stmt;
|
||||
detail::statement_access::reset(
|
||||
stmt,
|
||||
boost::mysql::detail::com_stmt_prepare_ok_packet{stmt_id, 2, num_params, 0}
|
||||
);
|
||||
return stmt;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -5,8 +5,8 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_TEST_COMMON_CREATE_DIAGNOSTICS_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATE_DIAGNOSTICS_HPP
|
||||
#ifndef BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_DIAGNOSTICS_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_DIAGNOSTICS_HPP
|
||||
|
||||
#include <boost/mysql/diagnostics.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
158
test/common/creation/create_execution_state.hpp
Normal file
158
test/common/creation/create_execution_state.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_TEST_COMMON_CREATION_CREATE_EXECUTION_STATE_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_EXECUTION_STATE_HPP
|
||||
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/rows.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
#include <boost/mysql/detail/protocol/execution_state_impl.hpp>
|
||||
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
|
||||
#include <boost/mysql/impl/execution_state.hpp>
|
||||
#include <boost/mysql/impl/results.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "creation/create_message_struct.hpp"
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
struct resultset_spec
|
||||
{
|
||||
std::vector<detail::protocol_field_type> types;
|
||||
boost::mysql::rows r;
|
||||
detail::ok_packet ok;
|
||||
|
||||
bool empty() const noexcept { return types.empty(); }
|
||||
};
|
||||
|
||||
// execution_state_impl
|
||||
class exec_builder
|
||||
{
|
||||
detail::execution_state_impl res_;
|
||||
|
||||
public:
|
||||
exec_builder(bool append_mode) : res_(append_mode) {}
|
||||
|
||||
exec_builder& reset(std::vector<field_view>* storage)
|
||||
{
|
||||
res_.reset(res_.encoding(), storage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& reset(
|
||||
detail::resultset_encoding enc = detail::resultset_encoding::text,
|
||||
std::vector<field_view>* storage = nullptr
|
||||
)
|
||||
{
|
||||
res_.reset(enc, storage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& seqnum(std::uint8_t v)
|
||||
{
|
||||
res_.sequence_number() = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& meta(const std::vector<detail::protocol_field_type>& types)
|
||||
{
|
||||
res_.on_num_meta(types.size());
|
||||
for (auto type : types)
|
||||
{
|
||||
res_.on_meta(create_coldef(type), metadata_mode::minimal);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& rows(const boost::mysql::rows& r)
|
||||
{
|
||||
assert(r.num_columns() == res_.current_resultset_meta().size());
|
||||
res_.on_row_batch_start();
|
||||
for (auto rv : r)
|
||||
{
|
||||
field_view* storage = res_.add_row();
|
||||
for (auto f : rv)
|
||||
{
|
||||
*(storage++) = f;
|
||||
}
|
||||
}
|
||||
res_.on_row_batch_finish();
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& ok(const detail::ok_packet& pack)
|
||||
{
|
||||
if (res_.should_read_head())
|
||||
res_.on_head_ok_packet(pack);
|
||||
else
|
||||
res_.on_row_ok_packet(pack);
|
||||
return *this;
|
||||
}
|
||||
|
||||
exec_builder& resultset(const resultset_spec& spec, bool is_last = false)
|
||||
{
|
||||
detail::ok_packet actual_ok = spec.ok;
|
||||
if (!is_last)
|
||||
{
|
||||
actual_ok.status_flags |= detail::SERVER_MORE_RESULTS_EXISTS;
|
||||
}
|
||||
if (!spec.empty())
|
||||
{
|
||||
meta(spec.types);
|
||||
}
|
||||
if (!spec.r.empty())
|
||||
{
|
||||
rows(spec.r);
|
||||
}
|
||||
return ok(actual_ok);
|
||||
}
|
||||
|
||||
exec_builder& last_resultset(const resultset_spec& spec) { return resultset(spec, true); }
|
||||
|
||||
detail::execution_state_impl build() { return std::move(res_); }
|
||||
|
||||
execution_state build_state()
|
||||
{
|
||||
execution_state res;
|
||||
detail::execution_state_access::get_impl(res) = build();
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
inline results create_results(const std::vector<resultset_spec>& spec)
|
||||
{
|
||||
exec_builder builder(true);
|
||||
for (std::size_t i = 0; i < spec.size(); ++i)
|
||||
{
|
||||
builder.resultset(spec[i], i == spec.size() - 1);
|
||||
}
|
||||
results res;
|
||||
detail::results_access::get_impl(res) = builder.build();
|
||||
return res;
|
||||
}
|
||||
|
||||
inline detail::execution_state_impl& get_impl(execution_state& st)
|
||||
{
|
||||
return detail::execution_state_access::get_impl(st);
|
||||
}
|
||||
|
||||
inline detail::execution_state_impl& get_impl(results& r) { return detail::results_access::get_impl(r); }
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -5,11 +5,9 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BOOST_MYSQL_TEST_COMMON_CREATE_MESSAGE_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATE_MESSAGE_HPP
|
||||
#ifndef BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_MESSAGE_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_MESSAGE_HPP
|
||||
|
||||
#include <boost/mysql/common_server_errc.hpp>
|
||||
#include <boost/mysql/mysql_collations.hpp>
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/capabilities.hpp>
|
||||
@ -23,6 +21,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "buffer_concat.hpp"
|
||||
#include "creation/create_message_struct.hpp"
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
@ -66,105 +65,89 @@ inline std::vector<std::uint8_t> create_message(
|
||||
);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void serialize_to_vector(std::vector<std::uint8_t>& res, const Args&... args)
|
||||
{
|
||||
detail::serialization_context ctx(detail::capabilities(0));
|
||||
std::size_t size = detail::get_size(ctx, args...);
|
||||
res.resize(res.size() + size);
|
||||
ctx.set_first(res.data());
|
||||
detail::serialize(ctx, args...);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
std::vector<std::uint8_t> serialize_to_vector(const Args&... args)
|
||||
{
|
||||
std::vector<std::uint8_t> res;
|
||||
detail::serialization_context ctx(detail::capabilities(0));
|
||||
std::size_t size = detail::get_size(ctx, args...);
|
||||
res.resize(size);
|
||||
ctx.set_first(res.data());
|
||||
detail::serialize(ctx, args...);
|
||||
serialize_to_vector(res, args...);
|
||||
return res;
|
||||
}
|
||||
|
||||
inline detail::ok_packet create_ok_packet(
|
||||
std::uint64_t affected_rows = 0,
|
||||
std::uint64_t last_insert_id = 0,
|
||||
std::uint16_t status_flags = 0,
|
||||
std::uint16_t warnings = 0,
|
||||
string_view info = ""
|
||||
)
|
||||
class ok_msg_builder
|
||||
{
|
||||
return detail::ok_packet{
|
||||
detail::int_lenenc{affected_rows},
|
||||
detail::int_lenenc{last_insert_id},
|
||||
status_flags,
|
||||
warnings,
|
||||
detail::string_lenenc{info},
|
||||
};
|
||||
}
|
||||
ok_builder impl_;
|
||||
std::uint8_t seqnum_{};
|
||||
|
||||
inline std::vector<std::uint8_t> create_ok_packet_body(
|
||||
std::uint64_t affected_rows = 0,
|
||||
std::uint64_t last_insert_id = 0,
|
||||
std::uint16_t status_flags = 0,
|
||||
std::uint16_t warnings = 0,
|
||||
string_view info = "",
|
||||
std::uint8_t header = 0x00
|
||||
)
|
||||
{
|
||||
auto pack = create_ok_packet(affected_rows, last_insert_id, status_flags, warnings, info);
|
||||
auto res = serialize_to_vector(
|
||||
std::uint8_t(header),
|
||||
pack.affected_rows,
|
||||
pack.last_insert_id,
|
||||
pack.status_flags,
|
||||
pack.warnings
|
||||
);
|
||||
// When info is empty, it's actually omitted in the ok_packet
|
||||
if (!info.empty())
|
||||
public:
|
||||
ok_msg_builder() = default;
|
||||
ok_msg_builder seqnum(std::uint8_t v)
|
||||
{
|
||||
auto vinfo = serialize_to_vector(pack.info);
|
||||
concat(res, vinfo);
|
||||
seqnum_ = v;
|
||||
return *this;
|
||||
}
|
||||
ok_msg_builder& affected_rows(std::uint64_t v)
|
||||
{
|
||||
impl_.affected_rows(v);
|
||||
return *this;
|
||||
}
|
||||
ok_msg_builder& last_insert_id(std::uint64_t v)
|
||||
{
|
||||
impl_.last_insert_id(v);
|
||||
return *this;
|
||||
}
|
||||
ok_msg_builder& warnings(std::uint16_t v)
|
||||
{
|
||||
impl_.warnings(v);
|
||||
return *this;
|
||||
}
|
||||
ok_msg_builder& info(string_view v)
|
||||
{
|
||||
impl_.info(v);
|
||||
return *this;
|
||||
}
|
||||
ok_msg_builder& more_results(bool v)
|
||||
{
|
||||
impl_.more_results(v);
|
||||
return *this;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
inline std::vector<std::uint8_t> create_ok_packet_message(
|
||||
std::uint8_t seqnum,
|
||||
std::uint64_t affected_rows = 0,
|
||||
std::uint64_t last_insert_id = 0,
|
||||
std::uint16_t status_flags = 0,
|
||||
std::uint16_t warnings = 0,
|
||||
string_view info = "",
|
||||
std::uint8_t header = 0x00
|
||||
)
|
||||
{
|
||||
return create_message(
|
||||
seqnum,
|
||||
create_ok_packet_body(affected_rows, last_insert_id, status_flags, warnings, info, header)
|
||||
);
|
||||
}
|
||||
std::vector<std::uint8_t> build_body(std::uint8_t header = 0x00)
|
||||
{
|
||||
auto pack = impl_.build();
|
||||
auto res = serialize_to_vector(
|
||||
std::uint8_t(header),
|
||||
pack.affected_rows,
|
||||
pack.last_insert_id,
|
||||
pack.status_flags,
|
||||
pack.warnings
|
||||
);
|
||||
// When info is empty, it's actually omitted in the ok_packet
|
||||
if (!pack.info.value.empty())
|
||||
{
|
||||
auto vinfo = serialize_to_vector(pack.info);
|
||||
concat(res, vinfo);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
inline std::vector<std::uint8_t> create_eof_packet_message(
|
||||
std::uint8_t seqnum,
|
||||
std::uint64_t affected_rows = 0,
|
||||
std::uint64_t last_insert_id = 0,
|
||||
std::uint16_t status_flags = 0,
|
||||
std::uint16_t warnings = 0,
|
||||
string_view info = ""
|
||||
)
|
||||
{
|
||||
return create_ok_packet_message(
|
||||
seqnum,
|
||||
affected_rows,
|
||||
last_insert_id,
|
||||
status_flags,
|
||||
warnings,
|
||||
info,
|
||||
0xfe
|
||||
);
|
||||
}
|
||||
std::vector<std::uint8_t> build_ok() { return create_message(seqnum_, build_body(0)); }
|
||||
|
||||
std::vector<std::uint8_t> build_eof() { return create_message(seqnum_, build_body(0xfe)); }
|
||||
};
|
||||
|
||||
inline std::vector<std::uint8_t> create_err_packet_body(std::uint16_t code, string_view message = "")
|
||||
{
|
||||
detail::err_packet pack{
|
||||
code,
|
||||
detail::string_fixed<1>{},
|
||||
detail::string_fixed<5>{},
|
||||
detail::string_eof{message},
|
||||
};
|
||||
auto pack = create_err_packet(code, message);
|
||||
return serialize_to_vector(
|
||||
std::uint8_t(0xff),
|
||||
pack.error_code,
|
||||
@ -194,19 +177,7 @@ inline std::vector<std::uint8_t> create_coldef_message(
|
||||
string_view name = "mycol"
|
||||
)
|
||||
{
|
||||
boost::mysql::detail::column_definition_packet pack{
|
||||
detail::string_lenenc("def"),
|
||||
detail::string_lenenc("mydb"),
|
||||
detail::string_lenenc("mytable"),
|
||||
detail::string_lenenc("mytable"),
|
||||
detail::string_lenenc(name),
|
||||
detail::string_lenenc(name),
|
||||
mysql_collations::utf8_general_ci,
|
||||
10, // column_length
|
||||
type,
|
||||
0, // flags
|
||||
0, // decimals
|
||||
};
|
||||
auto pack = create_coldef(type, name);
|
||||
return create_message(
|
||||
seqnum,
|
||||
serialize_to_vector(
|
101
test/common/creation/create_message_struct.hpp
Normal file
101
test/common/creation/create_message_struct.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// 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_TEST_COMMON_CREATION_CREATE_MESSAGE_STRUCT_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_MESSAGE_STRUCT_HPP
|
||||
|
||||
#include <boost/mysql/string_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
class ok_builder
|
||||
{
|
||||
detail::ok_packet pack_{};
|
||||
|
||||
void flag(std::uint16_t f, bool value)
|
||||
{
|
||||
if (value)
|
||||
pack_.status_flags |= f;
|
||||
else
|
||||
pack_.status_flags &= ~f;
|
||||
}
|
||||
|
||||
public:
|
||||
ok_builder() = default;
|
||||
ok_builder& affected_rows(std::uint64_t v)
|
||||
{
|
||||
pack_.affected_rows.value = v;
|
||||
return *this;
|
||||
}
|
||||
ok_builder& last_insert_id(std::uint64_t v)
|
||||
{
|
||||
pack_.last_insert_id.value = v;
|
||||
return *this;
|
||||
}
|
||||
ok_builder& warnings(std::uint16_t v)
|
||||
{
|
||||
pack_.warnings = v;
|
||||
return *this;
|
||||
}
|
||||
ok_builder& more_results(bool v = true)
|
||||
{
|
||||
flag(detail::SERVER_MORE_RESULTS_EXISTS, v);
|
||||
return *this;
|
||||
}
|
||||
ok_builder& out_params(bool v = true)
|
||||
{
|
||||
flag(detail::SERVER_PS_OUT_PARAMS, v);
|
||||
return *this;
|
||||
}
|
||||
ok_builder& info(string_view v)
|
||||
{
|
||||
pack_.info.value = v;
|
||||
return *this;
|
||||
}
|
||||
detail::ok_packet build() { return pack_; }
|
||||
};
|
||||
|
||||
inline detail::err_packet create_err_packet(std::uint16_t code, string_view message = "")
|
||||
{
|
||||
return detail::err_packet{
|
||||
code,
|
||||
detail::string_fixed<1>{},
|
||||
detail::string_fixed<5>{},
|
||||
detail::string_eof{message},
|
||||
};
|
||||
}
|
||||
|
||||
inline detail::column_definition_packet create_coldef(
|
||||
detail::protocol_field_type type,
|
||||
string_view name = "mycol"
|
||||
)
|
||||
{
|
||||
return boost::mysql::detail::column_definition_packet{
|
||||
detail::string_lenenc("def"),
|
||||
detail::string_lenenc("mydb"),
|
||||
detail::string_lenenc("mytable"),
|
||||
detail::string_lenenc("mytable"),
|
||||
detail::string_lenenc(name),
|
||||
detail::string_lenenc(name),
|
||||
33, // utf8_general_ci
|
||||
10, // column_length
|
||||
type,
|
||||
0, // flags
|
||||
0, // decimals
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
54
test/common/creation/create_meta.hpp
Normal file
54
test/common/creation/create_meta.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// 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_TEST_COMMON_CREATION_CREATE_META_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_META_HPP
|
||||
|
||||
#include <boost/mysql/metadata.hpp>
|
||||
#include <boost/mysql/metadata_mode.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/common_messages.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
class meta_builder
|
||||
{
|
||||
detail::column_definition_packet pack_{};
|
||||
|
||||
public:
|
||||
meta_builder(detail::protocol_field_type t)
|
||||
{
|
||||
pack_.type = t;
|
||||
pack_.character_set = 33; // utf8_general_ci
|
||||
}
|
||||
meta_builder& flags(std::uint16_t v) noexcept
|
||||
{
|
||||
pack_.flags = v;
|
||||
return *this;
|
||||
}
|
||||
meta_builder& decimals(std::uint8_t v) noexcept
|
||||
{
|
||||
pack_.decimals = v;
|
||||
return *this;
|
||||
}
|
||||
meta_builder& collation(std::uint16_t v) noexcept
|
||||
{
|
||||
pack_.character_set = v;
|
||||
return *this;
|
||||
}
|
||||
metadata build() { return detail::metadata_access::construct(pack_, false); }
|
||||
};
|
||||
|
||||
inline metadata create_meta(detail::protocol_field_type t) { return meta_builder(t).build(); }
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
55
test/common/creation/create_row_message.hpp
Normal file
55
test/common/creation/create_row_message.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// 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_TEST_COMMON_CREATION_CREATE_ROW_MESSAGE_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_ROW_MESSAGE_HPP
|
||||
|
||||
#include <boost/mysql/field_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/protocol/protocol_types.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "creation/create_message.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
template <class... Args>
|
||||
std::vector<std::uint8_t> create_text_row_message(std::uint8_t seqnum, const Args&... args)
|
||||
{
|
||||
auto fields = make_fv_arr(args...);
|
||||
std::vector<std::uint8_t> res;
|
||||
for (field_view f : fields)
|
||||
{
|
||||
if (f.is_int64() || f.is_uint64())
|
||||
{
|
||||
auto s = f.is_int64() ? std::to_string(f.get_int64()) : std::to_string(f.get_uint64());
|
||||
detail::string_lenenc slenenc{s};
|
||||
serialize_to_vector(res, slenenc);
|
||||
}
|
||||
else if (f.is_string())
|
||||
{
|
||||
detail::string_lenenc slenenc{f.get_string()};
|
||||
serialize_to_vector(res, slenenc);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("create_text_row_message: type not implemented");
|
||||
}
|
||||
}
|
||||
return create_message(seqnum, std::move(res));
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
49
test/common/creation/create_statement.hpp
Normal file
49
test/common/creation/create_statement.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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_TEST_COMMON_CREATION_CREATE_STATEMENT_HPP
|
||||
#define BOOST_MYSQL_TEST_COMMON_CREATION_CREATE_STATEMENT_HPP
|
||||
|
||||
#include <boost/mysql/statement.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/access_fwd.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace boost {
|
||||
namespace mysql {
|
||||
namespace test {
|
||||
|
||||
class statement_builder
|
||||
{
|
||||
boost::mysql::detail::com_stmt_prepare_ok_packet pack_{};
|
||||
|
||||
public:
|
||||
statement_builder() = default;
|
||||
statement_builder& id(std::uint32_t v) noexcept
|
||||
{
|
||||
pack_.statement_id = v;
|
||||
return *this;
|
||||
}
|
||||
statement_builder& num_params(std::uint16_t v) noexcept
|
||||
{
|
||||
pack_.num_params = v;
|
||||
return *this;
|
||||
}
|
||||
statement build()
|
||||
{
|
||||
statement stmt;
|
||||
detail::statement_access::reset(stmt, pack_);
|
||||
return stmt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -24,8 +24,7 @@
|
||||
#include "../test_stream.hpp"
|
||||
#include "buffer_concat.hpp"
|
||||
|
||||
boost::mysql::test::test_stream::test_stream(fail_count fc, executor_type ex)
|
||||
: fail_count_(fc), executor_(ex)
|
||||
boost::mysql::test::test_stream::test_stream(fail_count fc, executor_type ex) : fail_count_(fc), executor_(ex)
|
||||
{
|
||||
}
|
||||
|
||||
@ -46,7 +45,7 @@ boost::mysql::test::test_stream::test_stream(read_behavior b, fail_count fc, exe
|
||||
{
|
||||
}
|
||||
|
||||
void boost::mysql::test::test_stream::add_message(
|
||||
boost::mysql::test::test_stream& boost::mysql::test::test_stream::add_message(
|
||||
const std::vector<std::uint8_t>& bytes,
|
||||
bool separate_reads
|
||||
)
|
||||
@ -56,12 +55,14 @@ void boost::mysql::test::test_stream::add_message(
|
||||
read_break_offsets_.insert(bytes_to_read_.size());
|
||||
}
|
||||
concat(bytes_to_read_, bytes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void boost::mysql::test::test_stream::set_read_behavior(read_behavior b)
|
||||
boost::mysql::test::test_stream& boost::mysql::test::test_stream::set_read_behavior(read_behavior b)
|
||||
{
|
||||
bytes_to_read_ = std::move(b.bytes_to_read);
|
||||
read_break_offsets_ = std::move(b.break_offsets);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class MutableBufferSequence>
|
||||
@ -70,8 +71,7 @@ struct boost::mysql::test::test_stream::read_op : boost::asio::coroutine
|
||||
test_stream& stream_;
|
||||
MutableBufferSequence buffers_;
|
||||
|
||||
read_op(test_stream& stream, const MutableBufferSequence& buffers)
|
||||
: stream_(stream), buffers_(buffers){};
|
||||
read_op(test_stream& stream, const MutableBufferSequence& buffers) : stream_(stream), buffers_(buffers){};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self)
|
||||
@ -110,8 +110,7 @@ struct boost::mysql::test::test_stream::write_op : boost::asio::coroutine
|
||||
test_stream& stream_;
|
||||
ConstBufferSequence buffers_;
|
||||
|
||||
write_op(test_stream& stream, const ConstBufferSequence& buffers)
|
||||
: stream_(stream), buffers_(buffers){};
|
||||
write_op(test_stream& stream, const ConstBufferSequence& buffers) : stream_(stream), buffers_(buffers){};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self)
|
||||
@ -132,10 +131,7 @@ template <
|
||||
class ConstBufferSequence,
|
||||
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::test::test_stream::async_write_some(
|
||||
const ConstBufferSequence& buffers,
|
||||
CompletionToken&& token
|
||||
)
|
||||
boost::mysql::test::test_stream::async_write_some(const ConstBufferSequence& buffers, CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose<CompletionToken, void(error_code, std::size_t)>(
|
||||
write_op<ConstBufferSequence>(*this, buffers),
|
||||
@ -153,10 +149,7 @@ std::size_t boost::mysql::test::test_stream::get_size_to_read(std::size_t buffer
|
||||
}
|
||||
|
||||
template <class MutableBufferSequence>
|
||||
std::size_t boost::mysql::test::test_stream::do_read(
|
||||
const MutableBufferSequence& buffers,
|
||||
error_code& ec
|
||||
)
|
||||
std::size_t boost::mysql::test::test_stream::do_read(const MutableBufferSequence& buffers, error_code& ec)
|
||||
{
|
||||
// Fail count
|
||||
error_code err = fail_count_.maybe_fail();
|
||||
@ -200,10 +193,7 @@ std::size_t boost::mysql::test::test_stream::do_read(
|
||||
}
|
||||
|
||||
template <class ConstBufferSequence>
|
||||
std::size_t boost::mysql::test::test_stream::do_write(
|
||||
const ConstBufferSequence& buffers,
|
||||
error_code& ec
|
||||
)
|
||||
std::size_t boost::mysql::test::test_stream::do_write(const ConstBufferSequence& buffers, error_code& ec)
|
||||
{
|
||||
// Fail count
|
||||
error_code err = fail_count_.maybe_fail();
|
||||
@ -220,8 +210,7 @@ std::size_t boost::mysql::test::test_stream::do_write(
|
||||
for (auto it = first; it != last && num_bytes_written < write_break_size_; ++it)
|
||||
{
|
||||
boost::asio::const_buffer buff = *it;
|
||||
std::size_t num_bytes_to_transfer = (std::min
|
||||
)(buff.size(), write_break_size_ - num_bytes_written);
|
||||
std::size_t num_bytes_to_transfer = (std::min)(buff.size(), write_break_size_ - num_bytes_written);
|
||||
concat(bytes_written_, buff.data(), num_bytes_to_transfer);
|
||||
num_bytes_written += num_bytes_to_transfer;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include "create_diagnostics.hpp"
|
||||
#include "creation/create_diagnostics.hpp"
|
||||
#include "network_result.hpp"
|
||||
|
||||
// Helper functions and classes to implement netmakers
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <boost/mysql/row.hpp>
|
||||
#include <boost/mysql/row_view.hpp>
|
||||
|
||||
#include <boost/mysql/detail/auxiliar/results_iterator.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
|
||||
#include <boost/mysql/detail/auxiliar/static_string.hpp>
|
||||
#include <boost/mysql/detail/protocol/constants.hpp>
|
||||
@ -129,6 +130,11 @@ inline std::ostream& operator<<(std::ostream& os, deserialize_errc v)
|
||||
}
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, results_iterator it)
|
||||
{
|
||||
return os << "results_iterator(" << static_cast<const void*>(it.obj()) << ", index=" << it.index() << ")";
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
@ -20,10 +20,7 @@ namespace test {
|
||||
|
||||
using test_channel = detail::channel<test_stream>;
|
||||
|
||||
inline test_channel create_channel(
|
||||
std::vector<std::uint8_t> messages = {},
|
||||
std::size_t buffer_size = 0
|
||||
)
|
||||
inline test_channel create_channel(std::vector<std::uint8_t> messages = {}, std::size_t buffer_size = 1024)
|
||||
{
|
||||
return test_channel(buffer_size, std::move(messages));
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <boost/mysql/connection.hpp>
|
||||
|
||||
#include "test_channel.hpp"
|
||||
#include "test_stream.hpp"
|
||||
|
||||
namespace boost {
|
||||
@ -18,6 +19,11 @@ namespace test {
|
||||
|
||||
using test_connection = connection<test_stream>;
|
||||
|
||||
inline test_channel& get_channel(test_connection& conn) noexcept
|
||||
{
|
||||
return boost::mysql::detail::connection_access::get_channel(conn);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace mysql
|
||||
} // namespace boost
|
||||
|
@ -67,10 +67,7 @@ public:
|
||||
};
|
||||
|
||||
// Constructors
|
||||
inline test_stream(
|
||||
fail_count fc = fail_count(),
|
||||
executor_type ex = boost::asio::system_executor()
|
||||
);
|
||||
inline test_stream(fail_count fc = fail_count(), executor_type ex = boost::asio::system_executor());
|
||||
|
||||
inline test_stream(
|
||||
std::vector<std::uint8_t> bytes_to_read,
|
||||
@ -85,18 +82,27 @@ public:
|
||||
);
|
||||
|
||||
// Setting test behavior
|
||||
inline void add_message(const std::vector<std::uint8_t>& bytes, bool separate_reads = true);
|
||||
inline void set_read_behavior(read_behavior b);
|
||||
void set_write_break_size(std::size_t size) noexcept { write_break_size_ = size; }
|
||||
void set_fail_count(const fail_count& fc) noexcept { fail_count_ = fc; }
|
||||
void set_executor(executor_type ex) { executor_ = ex; }
|
||||
inline test_stream& add_message(const std::vector<std::uint8_t>& bytes, bool separate_reads = true);
|
||||
inline test_stream& set_read_behavior(read_behavior b);
|
||||
test_stream& set_write_break_size(std::size_t size) noexcept
|
||||
{
|
||||
write_break_size_ = size;
|
||||
return *this;
|
||||
}
|
||||
test_stream& set_fail_count(const fail_count& fc) noexcept
|
||||
{
|
||||
fail_count_ = fc;
|
||||
return *this;
|
||||
}
|
||||
test_stream& set_executor(executor_type ex)
|
||||
{
|
||||
executor_ = ex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Getting test results
|
||||
std::size_t num_bytes_read() const noexcept { return num_bytes_read_; }
|
||||
std::size_t num_unread_bytes() const noexcept
|
||||
{
|
||||
return bytes_to_read_.size() - num_bytes_read_;
|
||||
}
|
||||
std::size_t num_unread_bytes() const noexcept { return bytes_to_read_.size() - num_bytes_read_; }
|
||||
const std::vector<std::uint8_t>& bytes_written() const noexcept { return bytes_written_; }
|
||||
|
||||
// Stream operations
|
||||
@ -114,15 +120,13 @@ public:
|
||||
|
||||
template <
|
||||
class MutableBufferSequence,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t))
|
||||
CompletionToken>
|
||||
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(const MutableBufferSequence& buffers, CompletionToken&& token);
|
||||
|
||||
template <
|
||||
class ConstBufferSequence,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, std::size_t))
|
||||
CompletionToken>
|
||||
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_write_some(ConstBufferSequence const& buffers, CompletionToken&& token);
|
||||
|
||||
|
@ -29,6 +29,8 @@ add_executable(
|
||||
crud.cpp
|
||||
handshake.cpp
|
||||
prepared_statements.cpp
|
||||
stored_procedures.cpp
|
||||
multi_queries.cpp
|
||||
reconnect.cpp
|
||||
db_specific.cpp
|
||||
database_types.cpp
|
||||
|
@ -66,6 +66,8 @@ unit-test boost_mysql_integrationtests
|
||||
crud.cpp
|
||||
handshake.cpp
|
||||
prepared_statements.cpp
|
||||
stored_procedures.cpp
|
||||
multi_queries.cpp
|
||||
reconnect.cpp
|
||||
db_specific.cpp
|
||||
database_types.cpp
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <boost/mysql/connection.hpp>
|
||||
#include <boost/mysql/date.hpp>
|
||||
#include <boost/mysql/execution_state.hpp>
|
||||
#include <boost/mysql/results.hpp>
|
||||
#include <boost/mysql/tcp.hpp>
|
||||
|
||||
@ -33,6 +34,7 @@ BOOST_FIXTURE_TEST_CASE(query_empty_select, tcp_network_fixture)
|
||||
conn.query("SELECT * FROM empty_table", result);
|
||||
|
||||
// Verify results
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
validate_2fields_meta(result.meta(), "empty_table");
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 0u);
|
||||
@ -41,6 +43,25 @@ BOOST_FIXTURE_TEST_CASE(query_empty_select, tcp_network_fixture)
|
||||
BOOST_TEST(result.info() == "");
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(query_empty_select_multifn, tcp_network_fixture)
|
||||
{
|
||||
connect();
|
||||
|
||||
// Issue query
|
||||
boost::mysql::execution_state st;
|
||||
conn.start_query("SELECT * FROM empty_table", st);
|
||||
BOOST_TEST_REQUIRE(st.should_read_rows());
|
||||
validate_2fields_meta(st.meta(), "empty_table");
|
||||
|
||||
// Read eof
|
||||
auto rv = conn.read_some_rows(st);
|
||||
BOOST_TEST(rv.empty());
|
||||
BOOST_TEST(st.affected_rows() == 0u);
|
||||
BOOST_TEST(st.warning_count() == 0u);
|
||||
BOOST_TEST(st.last_insert_id() == 0u);
|
||||
BOOST_TEST(st.info() == "");
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(query_insert, tcp_network_fixture)
|
||||
{
|
||||
connect();
|
||||
@ -51,6 +72,7 @@ BOOST_FIXTURE_TEST_CASE(query_insert, tcp_network_fixture)
|
||||
conn.query("INSERT INTO inserts_table (field_varchar, field_date) VALUES ('v0', '2010-10-11')", result);
|
||||
|
||||
// Verify results
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().empty());
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 1u);
|
||||
@ -73,6 +95,7 @@ BOOST_FIXTURE_TEST_CASE(query_update, tcp_network_fixture)
|
||||
conn.query("UPDATE updates_table SET field_int = field_int+10", result);
|
||||
|
||||
// Validate results
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().empty());
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 2u); // there are 3 rows, but 1 has field_int = NULL
|
||||
@ -95,6 +118,7 @@ BOOST_FIXTURE_TEST_CASE(query_delete, tcp_network_fixture)
|
||||
conn.query("DELETE FROM updates_table", result);
|
||||
|
||||
// Validate results
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().empty());
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 3u);
|
||||
@ -119,6 +143,7 @@ BOOST_FIXTURE_TEST_CASE(statement_update, tcp_network_fixture)
|
||||
// Execute it
|
||||
results result;
|
||||
conn.execute_statement(stmt, std::make_tuple(200, "f0"), result);
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().empty());
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 1u);
|
||||
@ -146,6 +171,7 @@ BOOST_FIXTURE_TEST_CASE(statement_delete, tcp_network_fixture)
|
||||
// Execute it
|
||||
results result;
|
||||
conn.execute_statement(stmt, std::make_tuple("f0"), result);
|
||||
BOOST_TEST(result.size() == 1u);
|
||||
BOOST_TEST(result.meta().empty());
|
||||
BOOST_TEST(result.rows().empty());
|
||||
BOOST_TEST(result.affected_rows() == 1u);
|
||||
|
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