mirror of
https://github.com/boostorg/mysql.git
synced 2026-02-15 13:12:21 +00:00
Merge branch 'develop' into buffering
This commit is contained in:
@@ -24,27 +24,38 @@ environment:
|
||||
B2_CI_VERSION: 1
|
||||
B2_CXXFLAGS: -permissive-
|
||||
B2_VARIANT: release,debug
|
||||
B2_CXXSTD: 11,14,17
|
||||
B2_CXXSTD: 11,14,17,20
|
||||
VS_YEAR: 2019
|
||||
matrix:
|
||||
# Older Visual Studio versions - doesn't support Linux Docker
|
||||
# Older Visual Studio versions - TODO
|
||||
# - FLAVOR: Visual Studio 2017
|
||||
# APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
# PLATFORM: x64
|
||||
# B2_TOOLSET: msvc-14.1
|
||||
# B2_CXXSTD: 17
|
||||
# VS_YEAR: 2017
|
||||
# Boost.Build
|
||||
# Boost.Build. C++17 is already tested in CMake, and
|
||||
# having all cxxstd's in a single job causes timeouts
|
||||
- FLAVOR: Visual Studio 2019
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
PLATFORM: x64
|
||||
B2_TOOLSET: msvc-14.2
|
||||
B2_CXXSTD: 11,14,17
|
||||
B2_CXXSTD: 11,14
|
||||
- FLAVOR: Visual Studio 2019
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
PLATFORM: x86
|
||||
B2_TOOLSET: msvc-14.2
|
||||
B2_CXXSTD: 11,14,17
|
||||
B2_CXXSTD: 11,14
|
||||
- FLAVOR: Visual Studio 2019
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
PLATFORM: x64
|
||||
B2_TOOLSET: msvc-14.2
|
||||
B2_CXXSTD: 20
|
||||
- FLAVOR: Visual Studio 2019
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
PLATFORM: x86
|
||||
B2_TOOLSET: msvc-14.2
|
||||
B2_CXXSTD: 20
|
||||
# CMake
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
|
||||
@@ -14,7 +14,11 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
if(BUILD_TESTING)
|
||||
set(_TESTING_ENABLED ON)
|
||||
endif()
|
||||
|
||||
|
||||
# Don't run integration testing unless explicitly requested
|
||||
option(BOOST_MYSQL_INTEGRATION_TESTS OFF "Whether to build and run integration tests or not")
|
||||
mark_as_advanced(BOOST_MYSQL_INTEGRATION_TESTS)
|
||||
|
||||
# Valgrind tests and Valgrind-friendly code (e.g. mark SSL buffers as initialized)
|
||||
option(BOOST_MYSQL_VALGRIND_TESTS OFF "Whether to run Valgrind tests or not (requires Valgrind)")
|
||||
mark_as_advanced(BOOST_MYSQL_VALGRIND_TESTS)
|
||||
|
||||
@@ -276,7 +276,7 @@ boostbook mysql
|
||||
:
|
||||
mysql_doc
|
||||
:
|
||||
<xsl:param>"boost.root=https://www.boost.org/doc/libs/1_76_0"
|
||||
<xsl:param>"boost.root=https://www.boost.org/doc/libs/1_78_0"
|
||||
<xsl:param>boost.image.src=images/proposed_for_boost.svg
|
||||
<xsl:param>boost.graphics.root=images/
|
||||
<xsl:param>nav.layout=none
|
||||
|
||||
@@ -34,8 +34,8 @@ information, if available.
|
||||
|
||||
[section:completion_tokens Completion tokens]
|
||||
|
||||
The following completion tokens can be used in any
|
||||
asyncrhonous operation within __Self__:
|
||||
Any completion token you may use with Boost.Asio can also be used
|
||||
with this library. Here are some of the most common:
|
||||
|
||||
* [*Callbacks]. You can pass in a callable (function pointer,
|
||||
function object) with the same signature as the handler
|
||||
@@ -94,6 +94,9 @@ asyncrhonous operation within __Self__:
|
||||
[link mysql.examples.query_async_coroutinescpp20 This example]
|
||||
demonstrates using C++20 coroutines to perform text
|
||||
queries.
|
||||
* Any other type that satisfies the __CompletionToken__ type requirements.
|
||||
We have listed the most common ones here, but you can craft your own
|
||||
and use it with this library's async operations.
|
||||
|
||||
[endsect]
|
||||
|
||||
@@ -102,7 +105,7 @@ asyncrhonous operation within __Self__:
|
||||
__Self__ also supports default completion tokens. Recall that
|
||||
some stream types may have an associated __Executor__ that
|
||||
has an associated default completion token type (see
|
||||
[asiolinkref default_completion_token default_completion_token]).
|
||||
[asiorelink default_completion_token default_completion_token]).
|
||||
If this is the case, you don't need to specify the
|
||||
__CompletionToken__ parameter in initiating functions,
|
||||
and the default will be used.
|
||||
@@ -111,6 +114,28 @@ demonstrates using default completion tokens with __Self__.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:cancellations_and_timeouts Cancellations and timeouts]
|
||||
|
||||
All async operations in this library support
|
||||
[@boost:/doc/html/boost_asio/overview/core/cancellation.html per-operation cancellation].
|
||||
All operations support only the `terminal` [asioreflink cancellation_type cancellation_type].
|
||||
This means that, if an async operation is cancelled, the [reflink connection] object
|
||||
is left in an unspecified state, after which you should close or destroy the connection.
|
||||
In particular, it is [*not] safe to retry the cancelled operation.
|
||||
|
||||
Supporting cancellation allows you to implement timeouts without explicit
|
||||
support from the library. [link mysql.examples.timeouts This example]
|
||||
demonstrates how to implement this pattern.
|
||||
|
||||
Note that cancellation happens at the Boost.Asio level, and not at the
|
||||
MySQL operation level. This means that, when cancelling an operation, the
|
||||
current network read or write will be cancelled. The operation may have
|
||||
already reached the server and be executed. As stated above, after an
|
||||
operation is cancelled, the connection is left in an unspecified state, and
|
||||
you should close or destroy it.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:sequencing Sequencing of operations]
|
||||
|
||||
The MySQL client-server protocol is strictly sequential. Every
|
||||
|
||||
@@ -14,7 +14,7 @@ available for each function involving network transfers.
|
||||
The different overloads differ in how they deal with errors.
|
||||
Most of them use a strategy based on [reflink error_code]'s
|
||||
(which is an alias for `boost::system::error_code`).
|
||||
Aditionally, some of them use the [reflink error_info]
|
||||
Additionally, some of them use the [reflink error_info]
|
||||
class. This is used as a container for additional
|
||||
information about the error, if available.
|
||||
For example, if you issue a query using [refmem connection query]
|
||||
@@ -63,8 +63,7 @@ two different origins:
|
||||
|
||||
* [*Server defined] error codes. These codes are defined by the MySQL
|
||||
server. They range between 1 and 0xffff. They are described
|
||||
in detail [mysqllink server-error-reference.html
|
||||
in the MySQL error reference].
|
||||
in detail [mysqlerrlink in the MySQL error reference].
|
||||
* [*Client defined] error codes. These are defined by __Self__,
|
||||
and are always greater than 0xffff.
|
||||
|
||||
|
||||
@@ -31,17 +31,17 @@ Examples make use of a database named `boost_mysql_examples`.
|
||||
The server hostname and credentials (username and password) are passed
|
||||
to the examples via the command line.
|
||||
|
||||
If you're using docker, you can use the `anarthal/mysql8` container,
|
||||
If you're using docker, you can use the `ghcr.io/anarthal/mysql8` container,
|
||||
which already ships with all the required configuration:
|
||||
|
||||
[!teletype]
|
||||
```
|
||||
# If you're on a system supporting UNIX sockets. Note that /var/run/mysqld
|
||||
# should be empty for this to work; you can specify a different directory, if it's not
|
||||
> docker run -p 3306:3306 -v /var/run/mysqld:/var/run/mysqld -d anarthal/mysql8
|
||||
> docker run -p 3306:3306 -v /var/run/mysqld:/var/run/mysqld -d ghcr.io/anarthal/mysql8
|
||||
|
||||
# If you're on a system that does not support UNIX sockets
|
||||
> docker run -p 3306:3306 -d anarthal/mysql8
|
||||
> docker run -p 3306:3306 -d ghcr.io/anarthal/mysql8
|
||||
```
|
||||
|
||||
Please note that this container is just for demonstrative purposes,
|
||||
@@ -185,6 +185,23 @@ __assume_setup__
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:timeouts Timeouts]
|
||||
|
||||
This example demonstrates how to use Boost.Asio's
|
||||
cancellation features to add timeouts to your async operations,
|
||||
including the ones provided by __Self__.
|
||||
For that purpose, it employs C++20 coroutines.
|
||||
If you are not familiar with them, look at
|
||||
[link mysql.examples.query_async_coroutinescpp20 this example]
|
||||
first.
|
||||
|
||||
__assume_setup__
|
||||
|
||||
[import ../../example/timeouts.cpp]
|
||||
[example_timeouts]
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:ssl Setting SSL options]
|
||||
|
||||
This example demonstrates how to configure SSL options
|
||||
@@ -194,7 +211,7 @@ The example employs synchronous functions with
|
||||
exceptions as error handling. __see_error_handling__
|
||||
|
||||
__assume_setup__ Additionally, you should run your MySQL server
|
||||
with some test certificates we created your you, just for this example.
|
||||
with some test certificates we created for you, just for this example.
|
||||
You can find them in this project's GitHub repository, under `BOOST_MYQL_ROOT/tools/ssl`.
|
||||
If you're using the docker container, the setup has already been done
|
||||
for you.
|
||||
|
||||
@@ -58,7 +58,7 @@ If you are using CMake (finding the library using [*find_package] or
|
||||
fetching it using [*FetchContent]), you can link against the [*Boost::mysql*]
|
||||
CMake target, which will take care of the details for you.
|
||||
|
||||
Boost.Mysql has been tested with the following versions of MySQL:
|
||||
Boost.Mysql has been tested with the following RDBMS systems:
|
||||
|
||||
* [@https://dev.mysql.com/downloads/mysql/5.7.html MySQL v5.7.29].
|
||||
* [@https://dev.mysql.com/downloads/mysql/8.0.html MySQL v8.0.19].
|
||||
@@ -70,7 +70,7 @@ Boost.Mysql has been tested with the following versions of MySQL:
|
||||
|
||||
I would like to specially acknowledge [@https://github.com/madmongo1 Richard Hodges] (hodges.r@gmail.com)
|
||||
for his invaluable technical guidance during development. Thanks also to
|
||||
[@https://github.com/LeonineKing1199 Christian Mazakas] for his ideas in early stages,
|
||||
Christian Mazakas for his ideas in early stages,
|
||||
and to [@https://github.com/vinniefalco Vinnie Falco] for writing
|
||||
__Beast__, my source of inspiration, and [@https://github.com/boostorg/docca
|
||||
docca], which is used to generate these pages.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
[library Boost.Mysql
|
||||
[quickbook 1.7]
|
||||
[copyright 2019 - 2021 Ruben Perez]
|
||||
[copyright 2019 - 2022 Ruben Perez]
|
||||
[id mysql]
|
||||
[purpose MySQL client library]
|
||||
[license
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
[template nochunk[] [block '''<?dbhtml stop-chunking?>''']]
|
||||
[template mdash[] '''— ''']
|
||||
[template include_file[path][^<'''<ulink url="https://github.com/anarthal/mysql-asio/blob/master/include/'''[path]'''">'''[path]'''</ulink>'''>]]
|
||||
[template include_file[path][^<'''<ulink url="https://github.com/anarthal/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>''']
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]]
|
||||
[template beastreflink[id term][@boost:/doc/html/boost_beast/reference/[id].html [^boost::beast::[term]]]]
|
||||
[template mysqllink[id text][@https://dev.mysql.com/doc/refman/8.0/en/[id] [text]]]
|
||||
[template mysqlerrlink[text][@https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html [text]]]
|
||||
[template mysqlerrlink2[id text][@https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#[id] [text]]]
|
||||
|
||||
[def __Stream__ [reflink2 Stream ['Stream]]]
|
||||
[def __SocketStream__ [reflink2 SocketStream ['SocketStream]]]
|
||||
@@ -44,7 +46,7 @@
|
||||
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
|
||||
[def __Beast__ [@boost:/libs/beast/index.html Boost.Beast]]
|
||||
[def __Coroutine__ [@boost:/libs/coroutine/index.html Boost.Coroutine]]
|
||||
[def __Self__ [@https://anarthal.github.io/boost-mysql/index.html Boost.Mysql]]
|
||||
[def __Self__ [@https://anarthal.github.io/boost/index.html Boost.Mysql]]
|
||||
[def __boost_optional__ [@boost:/libs/optional/index.html `boost::optional`]]
|
||||
[def __see_error_handling__ See [link mysql.error_handling this section] for more info on error handling.]
|
||||
[def __assume_setup__ This example assumes you have gone through the [link mysql.examples.setup setup].]
|
||||
@@ -97,6 +99,7 @@
|
||||
[include reconnecting.qbk]
|
||||
[include examples.qbk]
|
||||
[include types.qbk]
|
||||
[include tests.qbk]
|
||||
|
||||
[section:quickref Reference]
|
||||
[xinclude helpers/quickref.xml]
|
||||
|
||||
63
doc/qbk/tests.qbk
Normal file
63
doc/qbk/tests.qbk
Normal file
@@ -0,0 +1,63 @@
|
||||
[/
|
||||
Copyright (c) 2019-2022 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:tests Building and running the tests]
|
||||
|
||||
[teletype]
|
||||
|
||||
This section explains how to build and run this library's tests. It is not intended
|
||||
for the regular user.
|
||||
|
||||
This library has both unit and integration tests. Considering the different flavors
|
||||
the MySQL server has (v5.x, v8.x and MariaDB, with sutile differences) and the complex nature
|
||||
of the client/server protocol, we have given certain weight to the latter. Additionally, all
|
||||
examples are also built and run as integration tests, too (as they require access to a real database server).
|
||||
|
||||
By default, the build will only compile and run unit tests (i.e. if you run `b2 libs/mysql/test` or `cmake`
|
||||
with no special args). If you want to run the integration tests and the examples, you need a real database server.
|
||||
|
||||
If you are using `docker`, you can use one of the following images:
|
||||
|
||||
* `ghcr.io/anarthal/mysql8`
|
||||
* `ghcr.io/anarthal/mysql5`
|
||||
* `ghcr.io/anarthal/mariadb`
|
||||
|
||||
You can run the containers as follows:
|
||||
```
|
||||
# If you're on a system supporting UNIX sockets. Note that /var/run/mysqld
|
||||
# should be empty for this to work; you can specify a different directory, if it's not
|
||||
> docker run -p 3306:3306 -v /var/run/mysqld:/var/run/mysqld -d <IMAGE_NAME> # replace by the image you've chosen
|
||||
|
||||
# If you're on a system that does not support UNIX sockets
|
||||
> docker run -p 3306:3306 -d ghcr.io/anarthal/mysql8
|
||||
```
|
||||
|
||||
If you are using your own database server, you will need to perform the following steps:
|
||||
|
||||
* Run `example/db_setup.sql` and `test/integration/db_setup.sql` as the root user. If you are running a MySQL 8.x server,
|
||||
run also `test/integration/db_setup_sha256.sql`.
|
||||
* Install the SSL certificates in `tools/ssl` in your MySQL server and change your config file so that your server uses them.
|
||||
More information [mysqllink using-encrypted-connections.html here].
|
||||
|
||||
Next, define the following environment variables:
|
||||
|
||||
* If your database server is NOT running in `localhost`, define `BOOST_MYSQL_SERVER_HOST` to the host where it is running.
|
||||
If you are using the Docker image as provided in this document, you don't need this.
|
||||
* If your system does not support UNIX sockets or your socket path is different than MySQL's default (`/var/run/mysqld/mysqld.sock`),
|
||||
define `BOOST_MYSQL_NO_UNIX_SOCKET_TESTS=1`.
|
||||
* If you are using MySQL 5.x or MariaDB, define `BOOST_MYSQL_NO_SHA256_TESTS=1`. These servers don't support part of the functionality we test.
|
||||
|
||||
If you are using `b2`, you can build the targets `boost/mysql/example//boost_mysql_all_examples`,
|
||||
`boost/mysql/test//boost_mysql_integrationtests` and `boost/mysql/test` to build and run the tests.
|
||||
|
||||
If you are using `cmake`, add `-DBOOST_MYSQL_INTEGRATION_TESTS=ON` to enable building and running integration tests
|
||||
and examples, and then test regularly with `ctest`.
|
||||
|
||||
[c++]
|
||||
|
||||
[endsect]
|
||||
@@ -78,7 +78,7 @@ for each one.
|
||||
to the time zone indicated by __time_zone__. The retrieved value of a __TIMESTAMP__
|
||||
field is thus a time point in some local time zone, dictated by the current
|
||||
__time_zone__ variable. As this variable can be changed programmatically, without
|
||||
the client knowing it, we represent __TIMESTAMP__s without their time zone,
|
||||
the client knowing it, we represent __TIMESTAMP__ s without their time zone,
|
||||
using [reflink datetime]. __TIMESTAMP__'s range is narrower than __DATETIME__'s,
|
||||
but we do not enforce it in the client.
|
||||
|
||||
|
||||
@@ -63,15 +63,20 @@ else()
|
||||
endif()
|
||||
|
||||
# Build and run all the examples
|
||||
add_example(tutorial TRUE ${SERVER_HOST})
|
||||
add_example(value TRUE ${SERVER_HOST})
|
||||
add_example(query_sync TRUE ${SERVER_HOST})
|
||||
add_example(query_async_callbacks TRUE ${SERVER_HOST})
|
||||
add_example(query_async_coroutines FALSE ${SERVER_HOST})
|
||||
add_example(query_async_coroutinescpp20 TRUE ${SERVER_HOST})
|
||||
add_example(query_async_futures TRUE ${SERVER_HOST})
|
||||
add_example(metadata TRUE ${SERVER_HOST})
|
||||
add_example(prepared_statements TRUE ${SERVER_HOST})
|
||||
add_example(default_completion_tokens TRUE ${SERVER_HOST})
|
||||
add_example(unix_socket TRUE "/var/run/mysqld/mysqld.sock")
|
||||
add_example(ssl TRUE ${SERVER_HOST})
|
||||
if (BOOST_MYSQL_INTEGRATION_TESTS)
|
||||
add_example(tutorial TRUE ${SERVER_HOST})
|
||||
add_example(value TRUE ${SERVER_HOST})
|
||||
add_example(query_sync TRUE ${SERVER_HOST})
|
||||
add_example(query_async_callbacks TRUE ${SERVER_HOST})
|
||||
add_example(query_async_coroutines FALSE ${SERVER_HOST})
|
||||
add_example(query_async_coroutinescpp20 TRUE ${SERVER_HOST})
|
||||
add_example(query_async_futures TRUE ${SERVER_HOST})
|
||||
add_example(metadata TRUE ${SERVER_HOST})
|
||||
add_example(prepared_statements TRUE ${SERVER_HOST})
|
||||
add_example(default_completion_tokens TRUE ${SERVER_HOST})
|
||||
add_example(ssl TRUE ${SERVER_HOST})
|
||||
add_example(timeouts TRUE ${SERVER_HOST})
|
||||
if ("$ENV{BOOST_MYSQL_NO_UNIX_SOCKET_TESTS}" STREQUAL "")
|
||||
add_example(unix_socket TRUE "/var/run/mysqld/mysqld.sock")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -14,8 +14,8 @@ if $(hostname) = ""
|
||||
hostname = "localhost" ;
|
||||
}
|
||||
|
||||
# Regular examples
|
||||
local default_examples =
|
||||
# Example list
|
||||
local examples_no_unix =
|
||||
tutorial
|
||||
value
|
||||
query_sync
|
||||
@@ -27,11 +27,16 @@ local default_examples =
|
||||
prepared_statements
|
||||
default_completion_tokens
|
||||
ssl
|
||||
timeouts
|
||||
;
|
||||
|
||||
for local example in $(default_examples)
|
||||
local example_targets ;
|
||||
|
||||
# Non-UNIX
|
||||
for local example in $(examples_no_unix)
|
||||
{
|
||||
unit-test "boost_mysql_example_$(example)"
|
||||
local example_name = "boost_mysql_example_$(example)" ;
|
||||
unit-test $(example_name)
|
||||
:
|
||||
"$(example).cpp"
|
||||
/boost/mysql//boost_mysql
|
||||
@@ -39,8 +44,23 @@ for local example in $(default_examples)
|
||||
:
|
||||
<testing.arg>"example_user example_password $(hostname)"
|
||||
;
|
||||
explicit $(example_name) ;
|
||||
example_targets += $(example_name) ;
|
||||
}
|
||||
|
||||
# UNIX sockets
|
||||
unit-test boost_mysql_example_unix_socket : unix_socket.cpp /boost/mysql//boost_mysql : <testing.arg>"example_user example_password" ;
|
||||
explicit boost_mysql_example_unix_socket ;
|
||||
# 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//boost_mysql
|
||||
:
|
||||
<testing.arg>"example_user example_password"
|
||||
;
|
||||
explicit boost_mysql_example_unix_socket ;
|
||||
example_targets += boost_mysql_example_unix_socket ;
|
||||
}
|
||||
|
||||
# Helper to run all of them in one go
|
||||
alias boost_mysql_all_examples : $(example_targets) ;
|
||||
explicit boost_mysql_all_examples ;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <boost/mysql.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/mysql.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
203
example/timeouts.cpp
Normal file
203
example/timeouts.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Copyright (c) 2019-2022 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_timeouts
|
||||
|
||||
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/mysql.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
using namespace boost::asio::experimental::awaitable_operators;
|
||||
using boost::mysql::error_code;
|
||||
using boost::asio::use_awaitable;
|
||||
|
||||
constexpr std::chrono::milliseconds TIMEOUT (2000);
|
||||
|
||||
void print_employee(const boost::mysql::row& employee)
|
||||
{
|
||||
std::cout << "Employee '"
|
||||
<< employee.values()[0] << " " // first_name (type boost::string_view)
|
||||
<< employee.values()[1] << "' earns " // last_name (type boost::string_view)
|
||||
<< employee.values()[2] << " dollars yearly\n"; // salary (type double)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions to check whether an async operation, launched in parallel with
|
||||
* a timer, was successful or instead timed out. The timer is always the first operation.
|
||||
* If the variant holds the first altrnative, that means that the timer fired before
|
||||
* the async operation completed, which means a timeout.
|
||||
*/
|
||||
template <class T>
|
||||
T check_timeout(std::variant<std::monostate, T>&& op_result)
|
||||
{
|
||||
if (op_result.index() == 0) {
|
||||
throw std::runtime_error("Operation timed out");
|
||||
}
|
||||
return std::get<1>(std::move(op_result));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We use Boost.Asio's cancellation capabilities to implement timeouts for our
|
||||
* asynchronous operations. This is not something specific to Boost.Mysql, and
|
||||
* can be used with any other asynchronous operation that follows Asio's model.
|
||||
*
|
||||
* Each time we invoke an asynchronous operation, we also call steady_timer::async_wait.
|
||||
* We then use Asio's overload for operator || to run the timer wait and the async operation
|
||||
* in parallel. Once the first of them finishes, the other operation is cancelled
|
||||
* (the behavior is similar to JavaScripts's Promise.race).
|
||||
* If we co_await the awaitable returned by operator ||, we get a std::variant<std::monostate, T>,
|
||||
* where T is the async operation's result type. If the timer wait finishes first (we have a timeout),
|
||||
* the variant will hold the std::monostate at index 0; otherwise, it will have the async operation's
|
||||
* result at index 1. The function check_timeout throws an exception in the case of timeout and
|
||||
* extracts the operation's result otherwise.
|
||||
*
|
||||
* If any of the MySQL specific operations result in a timeout, the connection is left
|
||||
* in an unspecified state. You should close it and re-open it to get it working again.
|
||||
*/
|
||||
boost::asio::awaitable<void> start_query(
|
||||
boost::mysql::tcp_ssl_connection& conn,
|
||||
boost::asio::ip::tcp::resolver& resolver,
|
||||
boost::asio::steady_timer& timer,
|
||||
const boost::mysql::connection_params& params,
|
||||
const char* hostname
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Resolve hostname
|
||||
timer.expires_after(TIMEOUT);
|
||||
auto endpoints = check_timeout(co_await (
|
||||
timer.async_wait(use_awaitable) ||
|
||||
resolver.async_resolve(
|
||||
hostname,
|
||||
boost::mysql::default_port_string,
|
||||
use_awaitable
|
||||
)
|
||||
));
|
||||
|
||||
// Connect to server. Note that we need to reset the timer before using it again.
|
||||
timer.expires_after(TIMEOUT);
|
||||
check_timeout(co_await (
|
||||
timer.async_wait(use_awaitable) ||
|
||||
conn.async_connect(*endpoints.begin(), params, use_awaitable)
|
||||
));
|
||||
|
||||
// Issue the query to the server
|
||||
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
|
||||
timer.expires_after(TIMEOUT);
|
||||
auto result = check_timeout(co_await (
|
||||
timer.async_wait(use_awaitable) ||
|
||||
conn.async_query(sql, use_awaitable)
|
||||
));
|
||||
|
||||
// Read all rows
|
||||
boost::mysql::row row;
|
||||
bool more_rows = true;
|
||||
while (more_rows)
|
||||
{
|
||||
timer.expires_after(TIMEOUT);
|
||||
more_rows = check_timeout(co_await (
|
||||
timer.async_wait(use_awaitable) ||
|
||||
result.async_read_one(row, use_awaitable)
|
||||
));
|
||||
print_employee(row);
|
||||
}
|
||||
|
||||
// Notify the MySQL server we want to quit, then close the underlying connection.
|
||||
check_timeout(co_await (
|
||||
timer.async_wait(use_awaitable) ||
|
||||
conn.async_close(use_awaitable)
|
||||
));
|
||||
}
|
||||
catch (const boost::system::system_error& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void main_impl(int argc, char** argv)
|
||||
{
|
||||
if (argc != 4)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char* hostname = argv[3];
|
||||
|
||||
// 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);
|
||||
boost::asio::steady_timer timer (ctx.get_executor());
|
||||
|
||||
// Connection parameters
|
||||
boost::mysql::connection_params params (
|
||||
argv[1], // username
|
||||
argv[2], // password
|
||||
"boost_mysql_examples" // database to use; leave empty or omit the parameter for no database
|
||||
);
|
||||
|
||||
// Resolver for hostname resolution
|
||||
boost::asio::ip::tcp::resolver resolver (ctx.get_executor());
|
||||
|
||||
/**
|
||||
* The entry point. We pass in a function returning
|
||||
* a boost::asio::awaitable<void>, as required.
|
||||
*/
|
||||
boost::asio::co_spawn(ctx.get_executor(), [&conn, &resolver, &timer, params, hostname] {
|
||||
return start_query(conn, resolver, timer, params, hostname);
|
||||
}, boost::asio::detached);
|
||||
|
||||
// Calling run will actually start the requested operations.
|
||||
ctx.run();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void main_impl(int, char**)
|
||||
{
|
||||
std::cout << "Sorry, your compiler does not support C++20 coroutines" << std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
main_impl(argc, argv);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
||||
@@ -124,6 +124,9 @@ public:
|
||||
/// Retrieves the executor associated to this object.
|
||||
executor_type get_executor() { return get_channel().get_executor(); }
|
||||
|
||||
/// The `Stream` type this connection is using.
|
||||
using next_layer_type = Stream;
|
||||
|
||||
/// Retrieves the underlying Stream object.
|
||||
Stream& next_layer() { return get_channel().next_layer(); }
|
||||
|
||||
@@ -204,7 +207,7 @@ public:
|
||||
*/
|
||||
template <
|
||||
typename EndpointType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -239,7 +242,7 @@ public:
|
||||
*/
|
||||
template <
|
||||
typename EndpointType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -288,7 +291,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -316,7 +319,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -359,7 +362,9 @@ public:
|
||||
* `void(boost::mysql::error_code, boost::mysql::resultset<Stream>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -384,7 +389,9 @@ public:
|
||||
* `void(boost::mysql::error_code, boost::mysql::resultset<Stream>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -421,7 +428,9 @@ public:
|
||||
* `void(boost::mysql::error_code, boost::mysql::prepared_statement<Stream>)`
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, prepared_statement<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::prepared_statement<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -444,7 +453,9 @@ public:
|
||||
* `void(boost::mysql::error_code, boost::mysql::prepared_statement<Stream>)`
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, prepared_statement<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::prepared_statement<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -490,7 +501,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -512,7 +523,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -567,7 +578,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -592,7 +603,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -48,7 +48,7 @@ void boost::mysql::connection<Stream>::connect(
|
||||
template <class Stream>
|
||||
template <
|
||||
class EndpointType,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::mysql::error_code)) CompletionToken
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken
|
||||
>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
@@ -93,7 +93,7 @@ void boost::mysql::connection<Stream>::handshake(
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::mysql::error_code)) CompletionToken>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code)
|
||||
@@ -141,7 +141,8 @@ boost::mysql::resultset<Stream> boost::mysql::connection<Stream>::query(
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, boost::mysql::resultset<Stream>)) CompletionToken>
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, boost::mysql::resultset<Stream>)
|
||||
@@ -188,7 +189,8 @@ boost::mysql::prepared_statement<Stream> boost::mysql::connection<Stream>::prepa
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, boost::mysql::prepared_statement<Stream>)) CompletionToken>
|
||||
void(::boost::mysql::error_code, ::boost::mysql::prepared_statement<Stream>)
|
||||
) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, boost::mysql::prepared_statement<Stream>)
|
||||
@@ -228,7 +230,7 @@ void boost::mysql::connection<Stream>::close()
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::mysql::error_code)) CompletionToken>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code)
|
||||
@@ -265,7 +267,7 @@ void boost::mysql::connection<Stream>::quit()
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::mysql::error_code)) CompletionToken>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code)
|
||||
|
||||
@@ -134,7 +134,8 @@ struct boost::mysql::prepared_statement<Stream>::async_execute_initiation
|
||||
|
||||
template <class Stream>
|
||||
template <class ValueForwardIterator, BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, boost::mysql::resultset<Stream>)) CompletionToken>
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, boost::mysql::resultset<Stream>)
|
||||
@@ -184,7 +185,7 @@ void boost::mysql::prepared_statement<Stream>::close()
|
||||
}
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::mysql::error_code)) CompletionToken>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code)
|
||||
|
||||
@@ -172,7 +172,7 @@ struct boost::mysql::resultset<Stream>::read_one_op
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, bool)) CompletionToken>
|
||||
void(::boost::mysql::error_code, bool)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, bool)
|
||||
@@ -303,7 +303,7 @@ struct boost::mysql::resultset<Stream>::read_many_op
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, std::vector<boost::mysql::row>)) CompletionToken>
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, std::vector<boost::mysql::row>)
|
||||
@@ -328,7 +328,7 @@ boost::mysql::resultset<Stream>::async_read_many(
|
||||
|
||||
template <class Stream>
|
||||
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(boost::mysql::error_code, std::vector<boost::mysql::row>)) CompletionToken>
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)) CompletionToken>
|
||||
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken,
|
||||
void(boost::mysql::error_code, std::vector<boost::mysql::row>)
|
||||
|
||||
@@ -153,7 +153,9 @@ public:
|
||||
*/
|
||||
template <
|
||||
class ValueCollection,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_value_collection<ValueCollection>
|
||||
@@ -187,7 +189,9 @@ public:
|
||||
*/
|
||||
template <
|
||||
class ValueCollection,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
|
||||
class EnableIf = detail::enable_if_value_collection<ValueCollection>
|
||||
@@ -257,7 +261,9 @@ public:
|
||||
*/
|
||||
template <
|
||||
class ValueForwardIterator,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -291,7 +297,9 @@ public:
|
||||
*/
|
||||
template <
|
||||
class ValueForwardIterator,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, resultset<Stream>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, ::boost::mysql::resultset<Stream>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -330,7 +338,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -350,7 +358,7 @@ public:
|
||||
* The handler signature for this operation is `void(boost::mysql::error_code)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
|
||||
@@ -137,7 +137,7 @@ class resultset
|
||||
* `void(boost::mysql::error_code, bool)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, bool))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, bool))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -163,7 +163,7 @@ class resultset
|
||||
* `void(boost::mysql::error_code, bool)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, bool))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, bool))
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -188,7 +188,9 @@ class resultset
|
||||
* `void(boost::mysql::error_code, std::vector<boost::mysql::row>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector<row>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -209,7 +211,9 @@ class resultset
|
||||
* `void(boost::mysql::error_code, std::vector<boost::mysql::row>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector<row>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -233,7 +237,9 @@ class resultset
|
||||
* `void(boost::mysql::error_code, std::vector<boost::mysql::row>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector<row>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
@@ -250,7 +256,9 @@ class resultset
|
||||
* `void(boost::mysql::error_code, std::vector<boost::mysql::row>)`.
|
||||
*/
|
||||
template <
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::vector<row>))
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(
|
||||
void(::boost::mysql::error_code, std::vector<::boost::mysql::row>)
|
||||
)
|
||||
CompletionToken
|
||||
BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)
|
||||
>
|
||||
|
||||
@@ -71,76 +71,82 @@ else()
|
||||
endif()
|
||||
|
||||
# Integration testing
|
||||
add_executable(
|
||||
boost_mysql_integrationtests
|
||||
if(BOOST_MYSQL_INTEGRATION_TESTS)
|
||||
add_executable(
|
||||
boost_mysql_integrationtests
|
||||
|
||||
# Utilities
|
||||
integration/utils/src/get_endpoint.cpp
|
||||
integration/utils/src/metadata_validator.cpp
|
||||
integration/utils/src/network_result.cpp
|
||||
integration/utils/src/er_network_variant.cpp
|
||||
integration/utils/src/sync_errc.cpp
|
||||
integration/utils/src/sync_exc.cpp
|
||||
integration/utils/src/async_callback.cpp
|
||||
integration/utils/src/async_callback_noerrinfo.cpp
|
||||
integration/utils/src/async_future.cpp
|
||||
integration/utils/src/async_coroutine.cpp
|
||||
integration/utils/src/async_coroutinecpp20.cpp
|
||||
integration/utils/src/default_completion_tokens.cpp
|
||||
# Utilities
|
||||
integration/utils/src/get_endpoint.cpp
|
||||
integration/utils/src/metadata_validator.cpp
|
||||
integration/utils/src/network_result.cpp
|
||||
integration/utils/src/er_network_variant.cpp
|
||||
integration/utils/src/sync_errc.cpp
|
||||
integration/utils/src/sync_exc.cpp
|
||||
integration/utils/src/async_callback.cpp
|
||||
integration/utils/src/async_callback_noerrinfo.cpp
|
||||
integration/utils/src/async_future.cpp
|
||||
integration/utils/src/async_coroutine.cpp
|
||||
integration/utils/src/async_coroutinecpp20.cpp
|
||||
integration/utils/src/default_completion_tokens.cpp
|
||||
|
||||
# Actual tests
|
||||
integration/connection.cpp
|
||||
integration/connect.cpp
|
||||
integration/handshake.cpp
|
||||
integration/query.cpp
|
||||
integration/prepare_statement.cpp
|
||||
integration/execute_statement.cpp
|
||||
integration/close_statement.cpp
|
||||
integration/resultset.cpp
|
||||
integration/prepared_statement_lifecycle.cpp
|
||||
integration/quit_connection.cpp
|
||||
integration/close_connection.cpp
|
||||
integration/reconnect.cpp
|
||||
integration/database_types.cpp
|
||||
integration/entry_point.cpp
|
||||
)
|
||||
target_include_directories(
|
||||
boost_mysql_integrationtests
|
||||
PRIVATE
|
||||
integration/utils/include
|
||||
)
|
||||
target_link_libraries(
|
||||
boost_mysql_integrationtests
|
||||
PRIVATE
|
||||
boost_mysql_testing
|
||||
Boost::coroutine
|
||||
)
|
||||
common_target_settings(boost_mysql_integrationtests)
|
||||
|
||||
if ("$ENV{BOOST_MYSQL_TEST_FILTER}" STREQUAL "")
|
||||
add_test(
|
||||
NAME boost_mysql_integrationtests
|
||||
COMMAND boost_mysql_integrationtests
|
||||
# Actual tests
|
||||
integration/connection.cpp
|
||||
integration/connect.cpp
|
||||
integration/handshake.cpp
|
||||
integration/query.cpp
|
||||
integration/prepare_statement.cpp
|
||||
integration/execute_statement.cpp
|
||||
integration/close_statement.cpp
|
||||
integration/resultset.cpp
|
||||
integration/prepared_statement_lifecycle.cpp
|
||||
integration/quit_connection.cpp
|
||||
integration/close_connection.cpp
|
||||
integration/reconnect.cpp
|
||||
integration/database_types.cpp
|
||||
integration/entry_point.cpp
|
||||
)
|
||||
else()
|
||||
add_test(
|
||||
NAME boost_mysql_integrationtests
|
||||
COMMAND boost_mysql_integrationtests "-t" $ENV{BOOST_MYSQL_TEST_FILTER}
|
||||
target_include_directories(
|
||||
boost_mysql_integrationtests
|
||||
PRIVATE
|
||||
integration/utils/include
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(
|
||||
boost_mysql_integrationtests
|
||||
PRIVATE
|
||||
boost_mysql_testing
|
||||
Boost::coroutine
|
||||
)
|
||||
common_target_settings(boost_mysql_integrationtests)
|
||||
|
||||
# If we are using memcheck, then run a subset of the integration tests
|
||||
# under valgrind. Coroutine tests don't work well under Valgrind, and
|
||||
# SSL tests are too slow. We do some other exclusions to reduce runtime
|
||||
if (BOOST_MYSQL_VALGRIND_TESTS)
|
||||
set(MEMCHECK_BASE_TEST_FILTER "!@ssl:!@async_coroutine")
|
||||
if ("$ENV{BOOST_MYSQL_TEST_FILTER}" STREQUAL "")
|
||||
set(TEST_FILTER ${MEMCHECK_BASE_TEST_FILTER})
|
||||
else()
|
||||
set(TEST_FILTER "${MEMCHECK_BASE_TEST_FILTER}:$ENV{BOOST_MYSQL_TEST_FILTER}")
|
||||
# Compose the test filter
|
||||
if (NOT "$ENV{BOOST_MYSQL_NO_UNIX_SOCKET_TESTS}" STREQUAL "")
|
||||
list(APPEND TEST_EXCLUSIONS "!@unix")
|
||||
endif()
|
||||
if (NOT "$ENV{BOOST_MYSQL_NO_SHA256_TESTS}" STREQUAL "")
|
||||
list(APPEND TEST_EXCLUSIONS "!@sha256")
|
||||
endif()
|
||||
string(JOIN ":" TEST_FILTER ${TEST_EXCLUSIONS})
|
||||
|
||||
if ("${TEST_FILTER}" STREQUAL "")
|
||||
add_test(
|
||||
NAME boost_mysql_integrationtests
|
||||
COMMAND boost_mysql_integrationtests
|
||||
)
|
||||
else()
|
||||
add_test(
|
||||
NAME boost_mysql_integrationtests
|
||||
COMMAND boost_mysql_integrationtests "-t" ${TEST_FILTER}
|
||||
)
|
||||
endif()
|
||||
|
||||
# If we are using memcheck, then run a subset of the integration tests
|
||||
# under valgrind. Coroutine tests don't work well under Valgrind, and
|
||||
# SSL tests are too slow. We do some other exclusions to reduce runtime
|
||||
if (BOOST_MYSQL_VALGRIND_TESTS)
|
||||
string(JOIN ":" TEST_FILTER ${TEST_EXCLUSIONS} "!@ssl" "!@async_coroutine")
|
||||
add_memcheck_test(
|
||||
NAME boost_mysql_integrationtests_memcheck
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests "-t" ${TEST_FILTER}
|
||||
)
|
||||
endif()
|
||||
add_memcheck_test(
|
||||
NAME boost_mysql_integrationtests_memcheck
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/boost_mysql_integrationtests "-t" ${TEST_FILTER}
|
||||
)
|
||||
endif()
|
||||
|
||||
35
test/Jamfile
35
test/Jamfile
@@ -6,15 +6,9 @@
|
||||
#
|
||||
|
||||
import os ;
|
||||
import sequence ;
|
||||
|
||||
# Command line args for integration tests
|
||||
local TEST_FILTER = [ os.environ BOOST_MYSQL_TEST_FILTER ] ;
|
||||
local TEST_COMMAND = "" ;
|
||||
if $(TEST_FILTER)
|
||||
{
|
||||
TEST_COMMAND = "-t $(TEST_FILTER)" ;
|
||||
}
|
||||
|
||||
# Stuff shared between unit and integration tests
|
||||
alias boost_mysql_test
|
||||
:
|
||||
/boost/mysql//boost_mysql
|
||||
@@ -26,6 +20,7 @@ alias boost_mysql_test
|
||||
<link>shared
|
||||
;
|
||||
|
||||
# Unit tests
|
||||
unit-test boost_mysql_unittests
|
||||
:
|
||||
boost_mysql_test
|
||||
@@ -52,7 +47,25 @@ unit-test boost_mysql_unittests
|
||||
unit/connection.cpp
|
||||
unit/entry_point.cpp
|
||||
;
|
||||
|
||||
|
||||
|
||||
# Integration test filtering
|
||||
local test_exclusions = "" ;
|
||||
if [ os.environ BOOST_MYSQL_NO_UNIX_SOCKET_TESTS ] != "" {
|
||||
test_exclusions += "!@unix" ;
|
||||
}
|
||||
if [ os.environ BOOST_MYSQL_NO_SHA256_TESTS ] != "" {
|
||||
test_exclusions += "!@sha256" ;
|
||||
}
|
||||
|
||||
local test_filter = [ sequence.join $(test_exclusions) : ":" ] ;
|
||||
|
||||
local test_command = "" ;
|
||||
if $(test_filter) != "" {
|
||||
test_command = "-t $(test_filter)" ;
|
||||
}
|
||||
|
||||
# Integration tests
|
||||
unit-test boost_mysql_integrationtests
|
||||
:
|
||||
boost_mysql_test
|
||||
@@ -84,6 +97,8 @@ unit-test boost_mysql_integrationtests
|
||||
integration/database_types.cpp
|
||||
integration/entry_point.cpp
|
||||
:
|
||||
<testing.arg>$(TEST_COMMAND)
|
||||
<testing.arg>$(test_command)
|
||||
<include>integration/utils/include
|
||||
;
|
||||
|
||||
explicit boost_mysql_integrationtests ;
|
||||
|
||||
@@ -13,7 +13,7 @@ BOOST_ROOT=/opt/boost
|
||||
export LD_LIBRARY_PATH=$BOOST_ROOT/lib:$LD_LIBRARY_PATH
|
||||
|
||||
if [ "$BOOST_MYSQL_DATABASE" != "mysql8" ]; then
|
||||
export BOOST_MYSQL_TEST_FILTER='!@sha256'
|
||||
export BOOST_MYSQL_NO_SHA256_TESTS=1
|
||||
fi
|
||||
|
||||
# Create the build directory
|
||||
@@ -26,6 +26,7 @@ cmake \
|
||||
-DCMAKE_PREFIX_PATH=$BOOST_ROOT \
|
||||
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
|
||||
-DCMAKE_CXX_STANDARD=$CMAKE_CXX_STANDARD \
|
||||
-DBOOST_MYSQL_INTEGRATION_TESTS=ON \
|
||||
$(if [ "$USE_VALGRIND" == 1 ]; then echo -DBOOST_MYSQL_VALGRIND_TESTS=ON; fi) \
|
||||
$(if [ "$USE_COVERAGE" == 1 ]; then echo -DBOOST_MYSQL_COVERAGE=ON; fi) \
|
||||
-G Ninja \
|
||||
|
||||
@@ -47,5 +47,5 @@ cp $BOOST_ROOT/libs/mysql/tools/user-config.jam ~/user-config.jam
|
||||
stdlib=$B2_STDLIB \
|
||||
-j4 \
|
||||
libs/mysql/test \
|
||||
libs/mysql/example \
|
||||
libs/mysql/example//boost_mysql_example_unix_socket
|
||||
libs/mysql/test//boost_mysql_integrationtests \
|
||||
libs/mysql/example//boost_mysql_all_examples
|
||||
|
||||
@@ -24,7 +24,8 @@ function Check-Call($blk)
|
||||
|
||||
$Env:Path += ";C:\Program Files\MySQL\MySQL Server 5.7\bin"
|
||||
$Env:Path = "C:\Python37-x64;" + $Env:Path # Override Python 2 setting
|
||||
$Env:BOOST_MYSQL_TEST_FILTER = "!@unix:!@sha256"
|
||||
$Env:BOOST_MYSQL_NO_UNIX_SOCKET_TESTS = "1"
|
||||
$Env:BOOST_MYSQL_NO_SHA256_TESTS = "1"
|
||||
$Env:BOOST_MYSQL_SERVER_HOST = "localhost"
|
||||
|
||||
### DB setup
|
||||
@@ -69,9 +70,7 @@ if ($Env:B2_TOOLSET) # Use Boost.Build
|
||||
Check-Call { git clone https://github.com/boostorg/boost-ci.git C:\boost-ci-cloned }
|
||||
Copy-Item -Path "C:\boost-ci-cloned\ci" -Destination ".\ci" -Recurse
|
||||
Remove-Item -Recurse -Force "C:\boost-ci-cloned"
|
||||
Check-Call { .\tools\build_windows_b2.bat `
|
||||
libs/mysql/example
|
||||
}
|
||||
Check-Call { .\tools\build_windows_b2.bat }
|
||||
}
|
||||
else # Use CMake
|
||||
{
|
||||
@@ -105,10 +104,12 @@ else # Use CMake
|
||||
"-DCMAKE_CXX_STANDARD=17" `
|
||||
"-DCMAKE_C_COMPILER=cl" `
|
||||
"-DCMAKE_CXX_COMPILER=cl" `
|
||||
"-DBOOST_MYSQL_INTEGRATION_TESTS=ON" `
|
||||
"-DBOOST_MYSQL_UNIX_SOCKET_EXAMPLE=OFF" `
|
||||
".."
|
||||
}
|
||||
Check-Call { cmake --build . -j --target install }
|
||||
Check-Call { ctest --verbose -E boost_mysql_example_unix_socket }
|
||||
Check-Call { ctest --verbose }
|
||||
Check-Call { python `
|
||||
..\tools\user_project_find_package\build.py `
|
||||
"-DCMAKE_PREFIX_PATH=$InstallPrefix;$BoostLocation"
|
||||
|
||||
@@ -28,12 +28,8 @@ IF DEFINED B2_VARIANT (SET B2_VARIANT=variant=%B2_VARIANT%)
|
||||
|
||||
cd %BOOST_ROOT%
|
||||
|
||||
IF DEFINED SCRIPT (
|
||||
call libs\%SELF%\%SCRIPT%
|
||||
) ELSE (
|
||||
set SELF_S=%SELF:\=/%
|
||||
REM Echo the complete build command to the build log
|
||||
ECHO b2 --abbreviate-paths libs/!SELF_S!/test %B2_TOOLCXX% %B2_CXXSTD% %B2_CXXFLAGS% %B2_DEFINES% %B2_THREADING% %B2_ADDRESS_MODEL% %B2_LINK% %B2_VARIANT% -j4 %*
|
||||
REM Now go build...
|
||||
b2 --abbreviate-paths libs/!SELF_S!/test %B2_TOOLCXX% %B2_CXXSTD% %B2_CXXFLAGS% %B2_DEFINES% %B2_THREADING% %B2_ADDRESS_MODEL% %B2_LINK% %B2_VARIANT% -j4 %*
|
||||
)
|
||||
b2 --abbreviate-paths ^
|
||||
libs/mysql/test ^
|
||||
libs/mysql/test//boost_mysql_integrationtests ^
|
||||
libs/mysql/example//boost_mysql_all_examples ^
|
||||
%B2_TOOLCXX% %B2_CXXSTD% %B2_CXXFLAGS% %B2_DEFINES% %B2_THREADING% %B2_ADDRESS_MODEL% %B2_LINK% %B2_VARIANT% -j4 %*
|
||||
|
||||
@@ -10,11 +10,12 @@ from sys import argv
|
||||
from subprocess import check_call
|
||||
from os import chdir, path
|
||||
|
||||
REPO_BASE = path.abspath(path.join(path.dirname(__file__), '..'))
|
||||
REPO_BASE = path.abspath(path.join(path.dirname(__file__), '..', '..'))
|
||||
|
||||
BASE_CONFIG = {
|
||||
'CMAKE_PREFIX_PATH': '/opt/boost-latest',
|
||||
'CMAKE_INSTALL_PREFIX': '/tmp/boost_mysql'
|
||||
'CMAKE_INSTALL_PREFIX': '/tmp/boost_mysql',
|
||||
'BOOST_MYSQL_INTEGRATION_TESTS': 'ON'
|
||||
}
|
||||
|
||||
CLANG_CONFIG = {
|
||||
@@ -23,6 +24,10 @@ CLANG_CONFIG = {
|
||||
}
|
||||
|
||||
ALL_CONFIGS = {
|
||||
'nounix': {
|
||||
**BASE_CONFIG,
|
||||
**CLANG_CONFIG,
|
||||
},
|
||||
'gcc-9': {
|
||||
**BASE_CONFIG,
|
||||
'CMAKE_C_COMPILER': 'gcc-9',
|
||||
|
||||
@@ -9,10 +9,11 @@ $ErrorActionPreference = "Stop"
|
||||
CHCP 65001
|
||||
$OutputEncoding = New-Object -typename System.Text.UTF8Encoding
|
||||
$Env:BOOST_ROOT = "C:\Users\User\source\repos\boost"
|
||||
$Env:BOOST_MYSQL_TEST_FILTER = "!@unix:!@sha256"
|
||||
$Env:BOOST_MYSQL_NO_UNIX_SOCKET_TESTS = "1"
|
||||
$Env:BOOST_MYSQL_NO_SHA256_TESTS = "1"
|
||||
$Env:BOOST_MYSQL_SERVER_HOST = "localhost"
|
||||
# $Env:OPENSSL_ROOT = "C:\Program Files\OpenSSL-Win64"
|
||||
$Env:OPENSSL_ROOT = "C:\Program Files (x86)\OpenSSL-Win32"
|
||||
|
||||
cd $Env:BOOST_ROOT
|
||||
.\b2 libs/mysql/test libs/mysql/example address-model=32 -j4
|
||||
.\b2 libs/mysql/test libs/mysql/test//boost_mysql_integrationtests libs/mysql/example//boost_mysql_all_examples address-model=32 -j4
|
||||
|
||||
@@ -11,7 +11,7 @@ from bs4 import BeautifulSoup
|
||||
import os
|
||||
from os import path
|
||||
|
||||
REPO_BASE = path.abspath(path.join(path.dirname(__file__), '..'))
|
||||
REPO_BASE = path.abspath(path.join(path.dirname(__file__), '..', '..'))
|
||||
DOC_PATH = path.join(REPO_BASE, 'doc', 'html')
|
||||
|
||||
def list_doc_files():
|
||||
|
||||
4
tools/scripts/file_headers.py
Normal file → Executable file
4
tools/scripts/file_headers.py
Normal file → Executable file
@@ -192,7 +192,7 @@ namespace mysql {{
|
||||
* \\details Some error codes are defined by the client library, and others
|
||||
* are returned from the server. For the latter, the numeric value and
|
||||
* string descriptions match the ones described in the MySQL documentation.
|
||||
* See [mysqllink server-error-reference.html the MySQL error reference]
|
||||
* See [mysqlerrlink the MySQL error reference]
|
||||
* for more info on server errors.
|
||||
*/
|
||||
enum class errc : int
|
||||
@@ -242,7 +242,7 @@ constexpr error_entry all_errors [] = {{
|
||||
def generate_errc_entry(err):
|
||||
if err.is_server:
|
||||
doc = ('Server error. Error number: {}, symbol: ' + \
|
||||
'[mysqllink server-error-reference.html\\#error_er_{} ER_{}].').format(
|
||||
'[mysqlerrlink2 error_er_{} ER_{}].').format(
|
||||
err.number, err.symbol, err.symbol.upper())
|
||||
else:
|
||||
if err.number == 0:
|
||||
|
||||
Reference in New Issue
Block a user