Multi-function operations

Added proper support for multi-function operations, segregating
  the API in single-function and multi-function operations.
  Removed resultset as I/O object, the read_xxx owning functions
  and read_all.
Unit test infrastructure for async testing.
Reference types lifetime fixes.
Removed connection::valid.
Fixed a serialization bug for statements with no parameters.
Binary protocol strings now use the type recommended by MySQL.
Refactored Jamfile to match best practices.
Updated description in libraries.json.

Close #82
Close #81
Close #73
Close #59
Close #58
Close #53
Close #41
Close #22
This commit is contained in:
Ruben Perez 2022-12-31 15:49:09 +01:00
parent a3c9844ef3
commit a975273490
172 changed files with 7108 additions and 6291 deletions

View File

@ -15,6 +15,7 @@ def _image(name):
def _b2_command(source_dir, toolset, cxxstd, variant, stdlib='native', address_model='64', server_host='localhost'):
return 'python tools/ci.py ' + \
'--clean=1 ' + \
'--build-kind=b2 ' + \
'--source-dir="{}" '.format(source_dir) + \
'--toolset={} '.format(toolset) + \
@ -28,6 +29,7 @@ def _b2_command(source_dir, toolset, cxxstd, variant, stdlib='native', address_m
def _cmake_command(source_dir, build_shared_libs=0, valgrind=0, coverage=0, generator='Ninja', db='mysql8', server_host='localhost'):
return 'python tools/ci.py ' + \
'--build-kind=cmake ' + \
'--clean=1 ' + \
'--generator="{}" '.format(generator) + \
'--source-dir="{}" '.format(source_dir) + \
'--build-shared-libs={} '.format(build_shared_libs) + \
@ -194,7 +196,7 @@ def docs():
"image": _image('build-docs'),
"pull": "if-not-exists",
"commands": [
'python tools/ci.py --build-kind=docs --source-dir=$(pwd)'
'python tools/ci.py --build-kind=docs --clean=1 --source-dir=$(pwd)'
]
}]
}

120
Jamfile
View File

@ -1,120 +0,0 @@
#
# Copyright (c) 2019-2020 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)
#
import os ;
import path ;
project /boost/mysql ;
# System libraries
if [ os.name ] = NT
{
local OPENSSL_ROOT_ENV = [ os.environ OPENSSL_ROOT ] ;
local OPENSSL_ROOT = "" ;
if $(OPENSSL_ROOT_ENV)
{
OPENSSL_ROOT = $(OPENSSL_ROOT_ENV) ;
}
else
{
OPENSSL_ROOT = "C:/OpenSSL" ;
}
project
: requirements
<include>$(OPENSSL_ROOT)/include
<variant>debug:<library-path>$(OPENSSL_ROOT)/lib
<target-os>windows<variant>debug:<library-path>$(OPENSSL_ROOT)/debug/lib
<variant>release:<library-path>$(OPENSSL_ROOT)/lib
;
if [ path.exists $(OPENSSL_ROOT)/lib/libssl.lib ]
{
echo "OpenSSL > 1.1.0. Including libssl" ;
lib ssl : : <target-os>windows <name>libssl ;
}
else if [ path.exists $(OPENSSL_ROOT)/lib/ssleay32.lib ]
{
echo "OpenSSL < 1.1.0. Including ssleay32" ;
lib ssl : : <target-os>windows <name>ssleay32 ;
}
else
{
lib ssl : : <link>shared ;
}
if [ path.exists $(OPENSSL_ROOT)/lib/libcrypto.lib ]
{
echo "OpenSSL > 1.1.0. Including libcrypto" ;
lib crypto : : <target-os>windows <name>libcrypto ;
}
else if [ path.exists $(OPENSSL_ROOT)/lib/libeay32.lib ]
{
echo "OpenSSL < 1.1.0. Including libeay32" ;
lib crypto : : <target-os>windows <name>libeay32 ;
}
else
{
lib crypto : : <link>shared ;
}
}
else
{
local OPENSSL_ROOT = [ os.environ OPENSSL_ROOT ] ;
if $(OPENSSL_ROOT)
{
project
: requirements
<include>$(OPENSSL_ROOT)/include
<library-path>$(OPENSSL_ROOT)/lib
;
}
lib ssl : : <link>shared ;
lib crypto : : <link>shared ;
}
# Requirements to use across targets
local requirements =
<include>../include
<define>BOOST_ALL_NO_LIB=1
<define>BOOST_ASIO_NO_DEPRECATED=1
<define>BOOST_ASIO_DISABLE_BOOST_ARRAY=1
<define>BOOST_ASIO_DISABLE_BOOST_BIND=1
<define>BOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
<define>BOOST_ASIO_DISABLE_BOOST_REGEX=1
<define>BOOST_ASIO_HAS_DEFAULT_FUNCTION_TEMPLATE_ARGUMENTS=1
<define>BOOST_COROUTINES_NO_DEPRECATION_WARNING=1
<define>BOOST_ALLOW_DEPRECATED_HEADERS=1
<toolset>msvc:<cxxflags>"/bigobj /Zc:__cplusplus"
<toolset>msvc-14.1:<cxxflags>"/permissive-"
<toolset>msvc-14.2:<cxxflags>"/permissive-"
<toolset>msvc:<define>_SCL_SECURE_NO_WARNINGS=1
<toolset>msvc:<define>_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING
<toolset>msvc:<define>_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING
<toolset>msvc:<define>_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
<target-os>linux:<define>_XOPEN_SOURCE=600
<target-os>linux:<define>_GNU_SOURCE=1
<target-os>windows:<define>_WIN32_WINNT=0x0601
;
# A static Asio library to speed up builds
lib asio
:
tools/asio.cpp
ssl
crypto
:
<link>static
$(requirements)
<define>BOOST_ASIO_SEPARATE_COMPILATION
:
:
$(requirements)
<define>BOOST_ASIO_SEPARATE_COMPILATION
;
alias mysql : asio ;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

4
doc/images/protocol.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

4
doc/images/resultset.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -89,7 +89,7 @@
[include 03_overview.qbk]
[include 04_queries.qbk]
[include 05_prepared_statements.qbk]
[include 06_resultsets.qbk]
[include 06_multi_function.qbk]
[include 07_fields.qbk]
[include 08_async.qbk]
[include 09_ssl.qbk]

View File

@ -13,7 +13,7 @@ Welcome to Boost.Mysql's tutorial. We will go through the simplest
possible piece of code using Boost.Mysql: a program that connects
to the MySQL server and issues the query `SELECT "Hello World!"`.
This tutorial assumes you have a running MySQL server listening
To run this tutorial, you need a running MySQL server listening
in localhost on port 3306 (the default one). You should have
the credentials of a valid MySQL user (username and password).
No further setup is needed.
@ -46,7 +46,8 @@ which accepts two parameters:
* The first one specifies the network address of the MySQL server.
As we are using TCP, this is a [asioreflink ip__tcp/endpoint ip::tcp::endpoint],
identifying the host and port to connect to.
identifying the host and port to connect to. We will use a `boost::asio::ip::tcp::resolver`
to convert the string hostname into a `boost::asio::ip::tcp::endpoint`.
* The second one is a collection of MySQL handshake parameters, including
username and password. This parameter must be an instance of
[reflink handshake_params]. You may also set other options like
@ -65,33 +66,25 @@ which accepts two parameters:
The next step is to issue the query to the server. We will use
[reflink2 connection.query tcp_ssl_connection::query],
which accepts a string containing a single SQL query and instructs
the server to run it. It returns a [reflink tcp_ssl_resultset]
object, which allows to read the query results:
the server to run it. It returns a [reflink resultset]
object, containing the rows returned by the query:
[tutorial_query]
[heading Reading the results]
[heading Obtaining the results]
A [reflink resultset] is an object that represents the result of a query.
Resultsets are not containers, but I/O objects:
they do not contain by themselves the entire result of the query, but allow
the user to read it using several methods. We will use
[reflink2 resultset.read_all tcp_ssl_resultset::read_all], which
reads all the rows in the resultset and places them in a [reflink rows]
object:
A [reflink resultset] is an object that holds the result of a query in memory.
To obtain the value we selected, we can write:
[tutorial_read]
[tutorial_resultset]
A [reflink rows] object is a matrix-like container of MySQL fields.
Each field is represented as a [reflink field_view], a variant-like class that
can hold any type allowed in MySQL.
[tutorial_fields]
The operation `all_rows.at(0)` returns the first row from the `all_rows`
container. From that row, we select the first field using `.at(0)` again.
We finally stream the returned `field_view`
to `std::cout`, which prints the expected phrase to the console.
Let's break this into steps:
* [refmem resultset rows] returns all the rows that this resultset contains.
It returns a [reflink rows_view], which is a matrix-like structure.
* `result.rows().at(0)` returns the first row in the resultset, represented as a [reflink row_view].
* `result.rows().at(0).at(0)` returns the first field in the first row. This is a
[reflink field_view], a variant-like class that can hold any type allowed in MySQL.
* The obtained `field_view` is streamed to `std::cout`.
[heading Closing the connection]
@ -104,7 +97,7 @@ we are closing the connection, and thus may fail.
[heading Final notes]
This concludes our __Self__ tutorial! You can now learn more about the
This concludes our tutorial! You can now learn more about the
core functionality of this library in the [link mysql.overview overview section].
You can also look at more complex [link mysql.examples examples].

View File

@ -8,6 +8,8 @@
[section:overview Overview]
[nochunk]
[import ../../example/tutorial.cpp]
This section briefly explains the library main classes and functions, and how to use them.
The following diagram shows the interaction between the main classes in the library:
@ -17,23 +19,42 @@ The following diagram shows the interaction between the main classes in the libr
[reflink connection] is the library's "entry point". A connection is an I/O object, templated on
a [reflink Stream] type. A `connection` contains an instance of that `Stream` type and additional state required
by the protocol. To create a `connection`, pass to its constructor the arguments required to construct a `Stream`.
by the protocol. `connection`'s constructor takes the same arguments as the underlying `Stream` constructor.
The library defines some typedefs to make things less verbose. The most common one is [reflink tcp_ssl_connection].
In this case, `Stream` is `boost::asio::ssl::stream<boost::asio::ip::tcp::socket>`,
which can be constructed from a `io_context::executor_type` and a `ssl::context`:
```
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);
```
[tutorial_connection]
Typedefs for other transports are also available. See [link mysql.other_streams this section] for more info.
[endsect]
[section Connection establishment]
The MySQL client/server protocol is session-oriented. Before anything else, you must perform session
establishment, usually by calling [refmem connection connect]. We've already gone through this in the
[link mysql.tutorial tutorial], so we won't repeat it here.
establishment, usually by calling [refmem connection connect]. This function performs two actions:
* It establishes the "physical" connection, by calling `connect()` on the underlying `Stream`
object. For a [reflink tcp_ssl_connection], this establishes the TCP connection.
* It performs the handshake with the MySQL server. This is part of the MySQL client/server
protocol. It performs authentication, sets session parameters like the default database
to use, and performs the TLS handshake, if required.
[refmem connection connect] takes two parameters, one for each of the above actions:
* The physical endpoint where the server is listening. For TCP streams, this is a
`boost::asio::ip::tcp::endpoint`. For UNIX sockets, it's a `boost::asio::local::stream_protocol::endpoint`.
For TCP, we can resolve a string hostname and port to an endpoint using a `resolver` object.
* [reflink handshake_params] to use for the handshake operation. This parameter doesn't depend on the `Stream`
type. See [link mysql.connparams this section] for more info.
[tutorial_connect]
Note that [refmem connection connect] can only be used with socket-like streams. If your stream
is not a socket, you must use the lower-level [refmem connection handshake] function. Please
read [link mysql.other_streams.non_sockets this section] for more info.
[endsect]
@ -61,7 +82,7 @@ The two main ways to use a connection are text queries and prepared statements:
]
[
```
tcp_ssl_resultset result;
resultset result;
conn.query("START TRANSACTION", result);
```
]
@ -75,33 +96,48 @@ The two main ways to use a connection are text queries and prepared statements:
]
[
```
tcp_ssl_connection conn;
conn.connect(/* server host, password... */);
tcp_ssl_statement stmt;
conn.prepare_statement("SELECT first_name FROM employees WHERE company_id = ? AND salary > ?");
conn.prepare_statement("SELECT first_name FROM employees WHERE company_id = ? AND salary > ?", stmt);
tcp_ssl_resultset result;
resultset result;
stmt.execute(std::make_tuple("HGS", 30000), result);
```
]
]
]
[reflink tcp_ssl_resultset] is a helper typedef for [reflink resultset]. Resultset objects
represent the result of a query or statement execution. They hold auxiliar information
about the operation, but don't contain the actual data generated by the operation.
The next sections show how to obtain it.
When you execute a text query or a prepared statement, you get a `resultset` object, which will be the subject
of the next section. We will delve deeper into prepared statements [link mysql.overview.statements later].
[reflink tcp_ssl_statement] is a helper typedef for [reflink statement], which represent
server-side prepared statements, as we've seen.
[endsect]
[section Resultsets]
In MySQL, a ['resultset] referes to the results generated by a SQL query. The [reflink resultset] class
is an in-memory reprentation of a MySQL resultset. The following diagram shows an approximate representation
of what a resultset looks like:
[$mysql/images/resultset.svg [align center] [scale 125]]
We can see that a resultset is composed of three pieces of information:
* The actual rows generated by the SQL query: [refmem resultset rows]. We'll expand on this later.
* Metadata about the columns retrieved by the query: [refmem resultset meta].
* Additional information about the query execution, like the number of affected rows ([refmem resultset affected_rows])
or the number of warnings generated by the query ([refmem resultset warning_count]).
You can obtain a `resultset` by executing a text query ([refmem connection query]) or a prepared statement
([refmem statement execute]).
All SQL statements generate resultsets. Statements that generate no rows, like `INSERT`s, generate empty resultsets
(i.e. `result.rows().empty() == true`). The interface to execute `SELECT`s and `INSERT`s is the same.
[endsect]
[section Rows and fields]
Values retrieved from SQL queries are represented as variant-like objects called fields.
Fields are organized in rows. This library defines the following types to represent fields and rows:
We saw that [refmem resultset rows] returns a matrix-like data structure containing the rows
retrieved by SQL query. This library defines six data structures to represent MySQL data:
[variablelist
[
@ -130,149 +166,50 @@ Fields are organized in rows. This library defines the following types to repres
]
]
[endsect]
[refmem resultset rows] returns a [reflink rows_view] object. The memory for the rows is owned by the
`resultset` object. Indexing the returned view also returns view objects:
[section:read Reading rows]
```
// Populate a resultset object
resultset result;
conn.query("SELECT 'Hello world'", result);
There are three methods to read rows from a `resultset`:
// resultset::rows() returns a rows_view. The underlying memory is owned by the resultset
rows_view all_rows = result.rows();
[variablelist
[
[[refmem resultset read_one]]
[Reads rows one by one.]
]
[
[[refmem resultset read_all]]
[Reads all rows at once.]
]
[
[[refmem resultset read_some]]
[Reads rows in batches of unspecified size.]
]
]
// Indexing a rows_view yields a row_view. The underlying memory is owned by the resultset
row_view first_row = all_rows.at(0);
For each method, there is an "owning" version, which populates [reflink row] or [reflink rows]
objects by lvalue reference; and a "non-owning" version, which returns [reflink row_view]
or [reflink rows_view] objects, pointing to the `connection` internal buffers.
These view objects are valid until you perform the next operation involving network
transfers on the connection.
// Indexing a row_view yields a field_view. The underlying memory is owned by the resultset
field_view first_field = first_row.at(0); // Contains the string "Hello world"
```
The following table shows the most used methods. For a full reference, see
[link mysql.resultsets.read this section].
Views behave similarly to `std::string_view`. You must make sure that you don't use a view after the
storage its points to has gone out of scope. In this case, you must not use any of the views after the
`resultset` object has gone out of scope.
[table
[
[Function]
[Typical use]
[Use when...]
]
[
[
[refmem resultset read_one], owning
]
[
```
row r;
while (result.read_one(r))
{
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* [*By default], for resultsets of arbitrary size.
* When processing one row at a time is preferred.
* A good alternative to [refmem resultset read_all], when all rows may not fit in memory.
]
]
[
[
[refmem resultset read_all], owning
]
[
```
rows all_rows;
result.read_all(all_rows);
for (row_view r: all_rows)
{
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* [*By default], for small resultsets.
* When having all rows at the same time in memory is advantageous.
]
]
[
[
[refmem resultset read_some], non-owning
]
[
```
while (!result.complete())
{
rows_view row_batch = result.read_some(use_views);
As it happens with `std::string_view`, you can take ownership of a view using its owning counterpart:
for (row_view r: row_batch)
{
// Process r as required
std::cout << r << std::endl;
}
}
```
]
[
* [*By default], when efficiency is key.
* There will be no server interaction that may invalidate the view while you process it.
]
]
]
```
// You may use all_rows_owning after result has gone out of scope
rows all_rows_owning {all_rows};
In the above snippets, we use [refmem resultset complete] to check whether there are more rows or not.
We say a `resultset` is complete when there are no more rows to read. The server indicates this by sending
us a special packet ("EOF packet" in MySQL slang). After a `resultset` is `complete`, you can access some
additional information about the query execution, like [refmem resultset last_insert_id]
and [refmem resultset affected_rows].
// You may use first_row_owning after result has gone out of scope
row first_row_owning {first_row};
[warning
Once you call `connection::query` or `statement::execute`, the server immediately sends all rows to the client.
[*You must read all rows], until the resultset is `complete`, before engaging in further operations.
Otherwise, the results are undefined.
]
// You may use first_field_owning after result has gone out of scope
field first_field_owning {first_field};
```
[endsect]
[section:rows_fields Using rows and fields]
[section:fields Using fields]
All row types support random access iterators, as well as `at()` and `operator[]` with the usual semantics.
[reflink field] and [reflink field_view] are specialized variant-like types that can hold any type
you may find in a MySQL table. Once you obtain a field, you can access its contents using the following functions:
When iterating or indexing a [reflink row] object, you obtain a [reflink field_view], which is an object
with reference semantics, pointing into `row`'s storage. You can "persist" a `field_view` by constructing a
[reflink field] from it.
```
tcp_ssl_resultset result;
row r;
conn.query("SELECT \"abc\", 42", result);
result.read_one(r);
field_view ref = r[0]; // ref points to the string "abc", references r
field f (ref); // f contains the string "abc"
```
Indexing or iterating a `row_view` also yields `field_view`s. Similarly, indexing
or iterating [reflink rows] or [reflink rows_view] objects yields a `row_view`.
[h4:fields Using fields]
Both `field` and `field_view` are specialized variant-like types that can hold any type
you may find in a MySQL table. The following accessor operations are supported:
* You can query a field's type by using [reflink2 field_view.kind kind],
which returns a [reflink field_kind] enum. You can query whether a field.
* You can query a field's type by using [refmemunq field_view kind],
which returns a [reflink field_kind] enum.
* You can query whether a field contains a certain type with `field::is_xxx`.
* You can get the underlying value with `field::as_xxx` and `field::get_xxx`.
The `as_xxx` functions are checked (they will throw an exception if the
@ -280,34 +217,66 @@ you may find in a MySQL table. The following accessor operations are supported:
in undefined behavior on type mismatch).
* You can stream and compare fields for equality.
Some examples:
For example:
```
tcp_ssl_resultset result;
row r;
resultset result;
conn.query("SELECT \"abc\", 42", result);
result.read_one(r);
field_view f = r[0]; // ref points to the string "abc", references r
// Using the is_xxx and get_xxx accessors
field_view f = result.rows().at(0).at(0); // f points to the string "abc"
if (f.is_string())
{
// we know it's a string, unchecked access
boost::string_view s = f.get_string();
std::cout << s << std::endl;
std::cout << s << std::endl; // Use the string as required
}
else
{
// Oops, something went wrong - schema msimatch?
}
// Checked access. Throws if f doesn't contain an int
f = r[1];
std::int64_t value = f.as_int64();
std::cout << value << std::endl;
// Using the as_xxx accessor
f = result.rows().at(0).at(1);
std::int64_t value = f.as_int64(); // Checked access. Throws if f doesn't contain an int
std::cout << value << std::endl; // Use the int as required
```
The following table shows the mapping from MySQL types to C++ types:
`NULL` values are represented as field objects having `kind() == field_kind::null`.
You can check whether a value is `NULL` or not using [refmemunq field_view is_null].
In the following snippet, the `salary` column was defined as a nullable `double`.
This is how `NULL`s are typically handled:
```
resultset result;
conn.query(R"%(
SELECT 0 AS product_id, 'potatoes' as description UNION
SELECT 1, NULL UNION
SELECT 2, 'carrots'
)%", result);
for (row_view r : result.rows())
{
field_view description_fv = r.at(1);
if (description_fv.is_null())
{
// Handle the NULL value
// Note: description_fv.is_string() will return false here; NULL is represented as a separate type
std::cout << "No description for product_id " << r.at(0) << std::endl;
}
else
{
// Handle the non-NULL case. Get the underlying value and use it as you want
// If there is any schema mismatch (and description was not defined as VARCHAR), this will throw
boost::string_view description = description_fv.as_string();
// Use description as required
std::cout << "product_id " << r.at(0) << ": " << description << std::endl;
}
}
```
Every MySQL type is mapped to a single C++ type. The following table shows these mappings:
[table
[
@ -411,46 +380,119 @@ The following table shows the mapping from MySQL types to C++ types:
[link mysql.fields This section] contains more information about how the library maps
MySQL types to C++ types.
[h4 Handling NULL values]
[endsect]
`NULL` values are represented as `field`/`field_view` objects having `kind() == field_kind::null`.
You can check whether a value is `NULL` or not using [refmemunq field_view is_null].
In the following snippet, the `salary` column was defined as a nullable `double`.
This is how `NULL`s are typically handled:
[section:statements Using prepared statements]
Until now, we've used simple text queries that did not contain any user-provided input.
In real world, most queries will contain some piece of user-provided input.
One approach could be to use string concatenation to construct a SQL query from user input,
and then execute it using `query()`. Avoid this approach as much as possible, as it can lead
to [*SQL injection vulnerabilities]. Instead, [*use prepared statements].
Prepared statements are server-side objects that represent a parameterized query. A statement is
represented in this library using the [reflink statement] template class. `statement` is a proxy I/O
object, as it references the `connection`'s stream and uses it for communication with the server.
Thus, you must keep the `connection` alive while you're using the statements it created.
Let's say you've got an inventory table, and you're writing a command-line program to get products
by ID. You've got the following table definition:
```
tcp_ssl_resultset result;
conn.query("SELECT last_name, salary FROM employees", result);
resultset result;
conn.query(R"%(
CREATE TEMPORARY TABLE products (
id VARCHAR(50) PRIMARY KEY,
description VARCHAR(256)
)
)", result);
conn.query("INSERT INTO products VALUES ('PTT', 'Potatoes'), ('CAR', 'Carrots')", result);
```
row r;
while(result.read_one(r))
You can prepare a statement to retrieve products by ID using:
```
tcp_ssl_statement stmt;
conn.prepare_statement("SELECT description FROM products WHERE id = ?", stmt);
```
[reflink tcp_ssl_statement] is a typedef for the `statement` class to make code less verbose.
You can execute the statement using [refmem statement execute]:
```
// Obtain the product_id from the user. product_id is untrusted input
const char* product_id = argv[2];
stmt.execute(std::make_tuple(product_id), result);
// Use result as required
```
You must pass as many actual parameters as `?` placeholders the statement has, as a `std::tuple`.
To learn more about prepared statements, please refer to [link mysql.prepared_statements this section].
[endsect]
[section Multi-function operations]
Until now, we've been using [refmem connection query] and [refmem statement execute], which send
an execution request to the server, read the response and all generated data into an in-memory `resultset` object.
Some use cases may not fit in this simple pattern. For example:
* When reading a very big resultset, it may not be efficient (or even possible) to completely
load it in memory. Reading rows in batches may be more adequate.
* If rows contain very long `TEXT` or `BLOB` fields, it may not be adequate to copy these values
from the network buffer into a `resultset`. A view-based approach may be better.
For these cases, we can break the `query()` or `execute()` operation into several steps,
in a ['multi-function operation] (the term is coined by this library). This example reads an entire
table row by row, which can be the case in an ETL process:
```
// Create the table and some sample data
// In a real system, body may be megabaytes long.
resultset result;
conn.query(R"%(
CREATE TEMPORARY TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR (256),
body TEXT
)
)%", result);
conn.query(R"%(
INSERT INTO posts (title, body) VALUES
('Post 1', 'A very long post body'),
('Post 2', 'An even longer post body')
)%", result);
// execution_state stores state about our operation, and must be passed to all functions
execution_state st;
// Writes the query request and reads the server response, but not the rows
conn.start_query("SELECT title, body FROM posts", st);
// post will be populated by read_one_row. When all rows have been read,
// read_one_row returns false
row post:
while (conn.read_one_row(st, post))
{
boost::string_view last_name = r.at(0).as_string();
field_view salary_fv = r.at(1);
if (salary_fv.is_null())
{
// Handle the NULL value
// Note: salary_fv.is_double() will return false here; NULL is represented as a separate type
std::cout << "No salary stored for employee " << last_name << std::endl;
}
else
{
// Handle the non-NULL case. Get the underlying value and use it as you want
// If there is any schema mismatch (and salary was not defined as DOUBLE), this will throw
double salary = salary_fv.as_double();
// Use salary as required
std::cout << "Employee " << last_name << " earns " << salary << "dollars yearly" << std::endl;
}
boost::string_view title = post.at(0).as_string();
boost::string_view body = post.at(0).as_string();
// use title and body as required
}
```
[note
Even though the column was defined as `DOUBLE`, for a `NULL` field, `salary_fv.is_double() == false` and
`salary_fv.as_double()` will throw an exception. At all effects, `NULL` values are not doubles!
[warning
Once you start a multi-function operation with [refmem connection start_query] or [refmem statement start_execution],
the server immediately sends all rows to the client. [*You must read all rows] before engaging in further operations.
Otherwise, you will encounter packet mismatches, which can lead to bugs and vulnerabilities!
]
Multi-function operations are powerful but complex. Only use them when there is a strong reason to do so.
Please refer to [link mysql.multi_function this section] for more information on these operations.
[endsect]
@ -458,6 +500,10 @@ This is how `NULL`s are typically handled:
The library offers asynchronous versions of each operation involving network transfers.
Following Asio's convention, these are named `async_xxx` (e.g. [refmem connection async_query]).
They follow Asio's async model, so they can be used with any `CompletionToken`, including callbacks
and coroutines. [link mysql.async This section] provides more info on this topic.
[h4 Single read and write per connection]
As mentioned, `connection` holds an internal `Stream` that is used for network transfers.
All network operations involve stream [*reads], stream [*writes], or [*both]. At any given point in time, for
@ -471,6 +517,7 @@ Doing this is illegal and should be avoided:
```
// Coroutine body
// DO NOT DO THIS!!!!
resultset result1, result2;
auto q1 = conn.async_query("SELECT 1", result1, use_awaitable);
auto q2 = conn.async_query("SELECT 2", result2, use_awaitable);
co_await (q1 && q2);
@ -480,34 +527,27 @@ If you need to perform queries in parallel, open more connections to the server.
[h4 Proxy I/O objects]
`resultset` and `statement` are proxy I/O objects: they use the stream created by the
As mentioned, `statement` is a proxy I/O object: it uses the stream created by the
`connection` to perform the required I/O. This means that [*read and write operations invoked
on resultsets and statements also count toward the `connection` maximum]. This is illegal, too:
on statements also count toward the `connection` maximum]. This is illegal, too:
```
// Coroutine body
// DO NOT DO THIS!!!!
tcp_ssl_resultset result1, result2;
rows all_rows;
co_await conn.async_query("SELECT 1", result1, use_awaitable);
resultset result1, result2;
tcp_ssl_statement stmt;
conn.prepare_statement("SELECT ?", stmt);
// Invokes two read operations in parallel - DO NOT DO THIS!!!
auto query_aw = conn.async_query("SELECT 2", result2, use_awaitable);
auto read_all_aw = result1.async_read_all(all_rows, use_awaitable);
co_await (query_aw && read_all_aw);
// Invokes two read and write operations in parallel on the same stream
// DO NOT DO THIS!!!
auto aw1 = conn.async_query("SELECT 1", result1, use_awaitable);
auto aw2 = stmt.async_execute(std::make_tuple(42), result2, use_awaitable);
co_await (aw1 && aw2);
```
If you're working in a multi-threading environment, please bear in mind that
[*network operations invoked on `statement` and `resultset` also mutate the
[*network operations invoked on `statement`s also mutate the
`connection`] that created them. Use strands as required to avoid race conditions.
[endsect]
[section:encoding Character sets, collations and time zones]
Encoding: set it on handshake or with SET NAMES and everything you send/receive will be in this encoding.
We don't do any handling. TODO.
[endsect]
[endsect]

View File

@ -7,43 +7,34 @@
[section:queries Text queries]
Text queries are started by calling the [refmem connection query]
and [refmem connection async_query] functions. They accept
a SQL query string as parameter, which will be executed in the server.
They return a [reflink resultset] with the query results.
To run a text query, use any of the following functions:
* [refmem connection query] or [refmem connection async_query], which execute the query and
read the generated results.
* [refmem connection start_query] and [refmem connection async_start_query], which initiate a
text query as a multi-function operation.
Almost any query that may be issued in the `mysql` command line
can be executed using this method. This includes `SELECT`s,
`UPDATE`s, `INSERT`s, `DELETE`s, `CREATE TABLE`s...
In particular, you may start transactions issuing a `START TRANSACTION`,
commit them using `COMMIT` and rolling them back using `ROLLBACK`.
You can also access the prepared statement functionality by
issuing `PREPARE` and `EXECUTE` commands. However, it is
advised to use [link mysql.prepared_statements
the dedicated prepared statement functionality]
in __Self__ for this task.
[heading Multi-function operation]
When you call [refmem connection query], you start a "multi-function" operation.
`query()` will send the query request to the server. The server then sends an initial response
and all the generated rows. `query()` will just read the initial response, and not the rows.
After a successful query, [*you must read all the generated rows] by calling any of the `read_xxx`
functions on `resultset`. If you start any other operation involving a read before doing this,
you will get undefined results.
[include helpers/query_strings_encoding.qbk]
[heading Limitations]
* You can only execute a single query at a time. Passing in several queries separated by semicolons
won't work.
* At the moment, there is no equivalent to `mysql_real_escape_string_quote` (TODO: link) to compose valid
text queries from user provided input in a safe way. Doing composition by hand can
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. 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 Use cases]
You should generally prefer prepared statements over text queries. Text queries can be useful for simple,
@ -55,13 +46,4 @@ non-parametrized queries:
Avoid text queries involving user input.
[heading Examples]
* [link mysql.examples.query_sync Sync query].
* [link mysql.examples.query_async_callbacks Async query, with callbacks].
* [link mysql.examples.query_async_futures Async query, with futures].
* [link mysql.examples.query_async_coroutines Async query, with Boost.Coroutine coroutines].
* [link mysql.examples.query_async_coroutinescpp20 Async query, with C++20 coroutines].
[endsect]
[endsect]

View File

@ -7,116 +7,114 @@
[section:prepared_statements Prepared statements]
[import ../../example/prepared_statements.cpp]
This section covers using [mysqllink sql-prepared-statements.html
server-side prepared statements]. The functionality is broadly
similar to the `PREPARE`, `EXECUTE` and `DEALLOCATE`
SQL commands. However, note that the functions and classes
here described are [*not based on SQL query composition],
but make use of dedicated protocol functionality.
This makes using this API preferable to using query composition
in terms of security and efficiency.
The rest of this section contains a detailed explanation of
prepared statement mechanics, together with some code
snippets. You can find the full code listing
[link mysql.examples.prepared_statements here].
server-side prepared statements]. You should use them whenever a query
contains parameters not known at compile-time.
[heading Preparing a statement]
To prepare a statement, call [refmem connection prepare_statement]
or [refmem connection async_prepare_statement]. You must pass in
a string containing the text of the SQL statement (similar
to how [link mysql.queries text queries] work). These functions
return a [reflink statement] object.
or [refmem connection async_prepare_statement], passing your statement
as a string. This yields a [reflink statement] object, which is templated
on the `Stream` type:
In addition to regular SQL, you can also use
question mark characters (`?`) to represent parameters
```
// Table setup
resultset result;
conn.query(R"%(
CREATE TEMPORARY TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
description VARCHAR(256),
price INT NOT NULL,
show_in_store TINYINT
)
)%", result);
// Prepare a statement to insert into this table
tcp_ssl_statement stmt;
conn.prepare_statement("INSERT INTO products (description, price, show_in_store) VALUES (?, ?, ?)", stmt);
```
The question mark characters (`?`) represent parameters
(as described [mysqllink prepare.html here]).
When you execute the statement (next section), you
provide values for each of the parameters you declared.
You don't need to escape or sanitize these values anyhow,
thus avoiding the possibility of SQL injection attacks.
The following prepares a statement with one
parameter (`conn` is a [reflink tcp_ssl_connection]):
[prepared_statements_prepare]
[include helpers/query_strings_encoding.qbk]
provide values for each of the parameters you declared, and the server
will use these values to run the statement.
[heading Executing a statement]
To execute a statement, use [refmem statement execute] or [refmem statement async_execute].
Executing a statement yields a [reflink resultset].
To execute a statement, use any of the following functions:
When executing a statement, you must pass in [*exactly as many parameters
as the statement has]. Failing to do so will result in an error.
Parameters are passed as a `std::tuple`, and in the same order as they
appear in the statement text. You can pass in built-in integers,
`float`, `double`, [reflink date], [reflink datetime], [reflink time],
[reflink field_view] and [reflink field] objects as parameters,
with the expected results. You can use `nullptr` objects to represent `NULL`
parameters. If your statement doesn't have any parameters, use the
[reflink no_statement_params] variable.
* [refmem statement execute] or [refmem statement async_execute], which execute the statement and
read the generated results.
* [refmem statement start_execution] and [refmem statement async_start_execution], which initiate a
statement execution as a multi-function operation.
The following executes the statement we prepared in the previous
section, binding the `first_name` parameter to `"Efficient"`:
[prepared_statements_execute]
Note that MySQL is pretty flexible and will perform casting to try to match
the supplied value to the required type, depending on the context.
In particular, for integer types, conversions to narrower integer types
will take place as long as the supplied value is in range. For example:
For example:
```
// Table created using: CREATE TABLE my_table (field_int TINYINT)
tcp_ssl_statement stmt;
conn.prepare_statement("INSERT INTO my_table (field_int) VALUES (?)", stmt);
tcp_ssl_resultset result;
std::int64_t value = 42; // OK, in range
stmt.execute(std::make_tuple(value), result);
value = 0xffff; // Oops, out of range
stmt.execute(std::make_tuple(value), result); // will throw
// description, price and show_in_store are not trusted, since they may
// have been read from a file or an HTTP endpoint
void insert_product(
tcp_ssl_statement& stmt,
boost::string_view description,
int price,
bool show_in_store
)
{
resultset result;
stmt.execute(std::make_tuple(description, price, int(show_in_store)), result);
}
```
To insert a `NULL` value, use the following:
Some observations:
* You must pass in [*exactly as many parameters
as the statement has]. Failing to do so will result in an error.
* You don't need to sanitize the parameters anyhow. The server takes care of it.
* Actual parameters are matched to `?` placeholders by order.
* Parameters are passed as a `std::tuple`. You can pass in built-in integers,
`float`, `double`, [reflink date], [reflink datetime], [reflink time],
[reflink field_view] and [reflink field] objects as parameters.
* `show_in_store` is passed as an `int` to `execute()`, but is defined as a `TINYINT`
(1 byte integer) in the table. As long as the passed integer is in range, MySQL
will perform the required conversions. Otherwise, `execute()` will fail with an error
(no undefined behavior is invoked).
You can also pass [reflink field_view]s and [reflink field]s as parameters. This is handy
to insert `NULL` values:
```
stmt.execute(std::make_tuple(nullptr), result);
// description, price and show_in_store are not trusted, since they may
// have been read from a file or an HTTP endpoint
void insert_product(
tcp_ssl_statement& stmt,
std::optional<boost::string_view> description,
int price,
bool show_in_store
)
{
// if description has a value, description_param will have kind() == field_kind::string
// and will point to it. Otherwise, description_param.kind() == field_kind::null
auto description_param = description ? field_view(*description) : field_view();
// Execute the insert
resultset result;
stmt.execute(std::make_tuple(description_param, price, int(show_in_store)), result);
}
```
For a full reference on the types you can pass as parameters when
executing a statement, see [link mysql.fields.cpp_to_mysql this section].
[heading Multi-function operation]
When you call [refmem statement execute], you start a "multi-function" operation.
`execute()` will send the execute request to the server. The server then sends an initial response
and all the generated rows. `execute()` will just read the initial response, and not the rows.
After a successful `execute()`, [*you must read all the generated rows] by calling any of the `read_xxx`
functions on `resultset`. If you start any other operation involving a read before doing this,
you will get undefined results.
[heading Closing a statement]
Prepared statements are created server-side, and
thus consume server resources. If you don't need a
[reflink statement] anymore, you can call
[refmem statement close] or
[refmem statement async_close] to instruct
the server to deallocate it.
Prepared statements are created server-side, and thus consume server resources. If you don't need a
[reflink statement] anymore, you can call [refmem statement close] or
[refmem statement async_close] to instruct the server to deallocate it.
Prepared statements are managed by the server on a
per-connection basis. Once you close your connection
with the server, all prepared statements you have
created using this connection will be automatically
Prepared statements are managed by the server on a per-connection basis. Once you close your connection
with the server, all prepared statements you have created using this connection will be automatically
deallocated.
If you are creating your prepared statements at the beginning
@ -132,4 +130,4 @@ does not perform any server-side deallocation of the statement.
This is because closing a statement involves a network
operation that may block your code or fail.
[endsect]
[endsect]

View File

@ -0,0 +1,222 @@
[/
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:multi_function Multi-function operations]
[nochunk]
Multi-function operations allow running operations as a set of separate
steps, which gives you better control over execution. They work by splitting
some of the reads and writes into several function calls.
You can use multi-function operations to execute text queries and prepared statements.
[heading Protocol primer]
To make a good use of multi-function operations, you should have a basic understanding
of the underlying protocol.
The protocol uses ['messages] to communicate. These are delimited by headers containing the message length.
All operations are initiated by the client, by sending a single ['request message], to which
the server responds with a set of ['response messages].
The diagram below shows the message exchange between client and server for text queries and statement
executions. Each arrow represents a message.
[$mysql/images/protocol.svg [align center]]
The message exchange is similar for text queries and prepared statements. The wire format varies, but the semantics
are the same.
There are two separate cases:
* If your query retrieved at least one column (even if no rows were generated), we're in ['case 1]. The server sends:
* An initial packet informing that the query executed correctly, and that we're in ['case 1].
* Some matadata packets describing the columns that the query retrieved. These become available
under [refmem resultset meta] and [refmem execution_state meta], and are necessary to parse the rows.
* The actual rows.
* An OK packet, which marks the end of the resultset and contains information like `last_insert_id` and
`affected_rows`.
* If your query didn't retrieve any column, we're in ['case 2]. The server will just send an OK packet,
with the same information as in ['case 1].
[refmem connection query] and [refmem statement execute] handle the full message exchange. In contrast,
[refmem connection start_query] and [refmem statement start_execution] will not read the rows, if any.
Some takeaways:
* The distinction between single-function and multi-function operations exists only
in the client. The wire messages exchanged by both are the same.
* There is no way to tell how many rows a resultset has upfront. You need to read row by row until
you find the OK packet marking the end of the resultset.
* When the server processes the request message, [*it sends all the response messages immediately].
These responses will be waiting in the client to be read. If you don't read [*all] of them,
subsequent operations will mistakenly read them as their response, causing packet mismatches.
Be careful and don't let this happen!
[heading Starting a multi-function operation]
Given the following setup:
```
// TODO: this is repeated from the overview section
resultset result;
conn.query(R"%(
CREATE TEMPORARY TABLE posts (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR (256),
body TEXT
)
)%", result);
conn.query(R"%(
INSERT INTO posts (title, body) VALUES
('Post 1', 'A very long post body'),
('Post 2', 'An even longer post body')
)%", result);
// This is not repeated
tcp_ssl_statement stmt;
conn.prepare_statement("SELECT title, body FROM posts", stmt);
```
You can start a multi-function operation using [refmem connection start_query] or [refmem statement start_execution]:
[table
[
[Text queries]
[Prepared statements]
]
[
[
```
execution_state st;
conn.start_query("SELECT title, body FROM posts", st);
```
]
[
```
execution_state st;
stmt.start_execution(std::make_tuple(), st); // The statement has no params, so an empty tuple is passed
```
]
]
]
[heading Reading rows]
Once the operation has been started, you can read rows in two ways:
* [refmem connection read_one_row] reads a single row per call.
* [refmem connection read_some_rows] reads a batch of an unspecified size.
The following table shows examples on how to use each method (where `st` is the [reflink execution_state]
you passed to the `start_query()` or `start_execution()` function):
[table
[
[Function]
[Typical use]
[Remarks]
]
[
[
[refmem connection read_one_row], non-owning
]
[
```
while (true)
{
// post will be valid until conn performs any other network operation
// when the OK packet is read, post will be an empty view
row_view post = conn.read_one_row(st);
// st.complete() returns true once the OK packet is received
if (st.complete())
break;
// Process post as required
std::cout << post << std::endl;
}
```
]
[
* When the final OK packet is found, `read_one_row` returns an empty view and `st.complete() == true`.
* May be slower than `read_some_rows`, but it may consume less memory.
* Calling `read_one_row` after reading the final OK packet returns an empty view.
]
]
[
[
[refmem connection read_some_rows], non-owning
]
[
```
// st.complete() returns true once the OK packet is received
while (!st.complete())
{
// row_batch will be valid until conn performs any other network operation
rows_view row_batch = conn.read_some_rows(st);
for (row_view post : row_batch)
{
// Process post as required
std::cout << post << std::endl;
}
}
```
]
[
* The final `row_batch` may or may not be empty, depending on the number of rows and their size.
* Usually the fastest alternative.
* Calling `read_some_rows` after reading the final OK packet returns an empty batch.
]
]
]
Both functions return view objects pointing into the connection's internal buffers. These views are valid
until the connection performs any other operation involving a network transfer.
[refmem execution_state complete] returns `true` after we've read the final OK packet for this resultset.
We use it to exit our read loop.
You can take ownserhip of the views using the [reflink row] and [reflink rows] classes. Note however that these
are immutable types (in that they don't have mutating functions other than assignment), designed to maximize memory reuse.
If you need to mutate a row, you can use [refmem row_view as_vector] to obtain a `std::vector<field>`.
Note that there is no need to distinguish between ['case 1] and ['case 2] in code, as reading rows for
a complete operation is well defined.
[heading Accessing metadata and OK packet data]
You can access metadata at any point, using [refmem execution_state meta]. This function returns a collection of [reflink metadata]
objects. There is one object for each column retrieved by the SQL query, and in the same order as in the query. You can find a bunch
of useful information in this object, like the column name, its type, whether it's a key or not, and so on.
You can access 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].
[heading:read_some_rows More on read_some_rows]
To properly understand `read_some_rows`, we need to know that every [reflink connection]
owns an internal *read buffer*, where packets sent by the server are stored.
It is a single, flat buffer, and you can configure its initial size using
[refmem handshake_params buffer_config] when establishing the connection.
The read buffer may be grown under certain circumstances to accomodate large messages.
`read_some_rows` gets the maximum number of rows that fit in the read buffer (without growing it)
performing a single `read_some_rows` operation on the stream (or using cached data).
If there are rows to read, `read_some_rows` guarantees to read at least one. This means that,
if doing what we described yields no rows (e.g. because of a large row that doesn't fit
into the read buffer), `read_some_rows` will grow the buffer or perform more reads until at least
one row has been read.
If you want to get the most of `read_some_rows`, customize the initial read buffer size
to maximize the number of rows that each batch retrieves.
[endsect]

View File

@ -1,331 +0,0 @@
[/
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:resultsets Resultsets]
[nochunk]
[reflink2 resultset Resultset objects] represent the result of a SQL query.
You can obtain a `resultset` by executing a [link mysql.queries text query]
or a [link mysql.prepared_statements prepared statement].
A `resultset` contains metadata and state about the query or statement being
executed, but not the actual rows.
[section:read Reading rows]
Recall the following points from [link mysql.overview.read this section]:
* [reflink row] and [reflink row_view] represent a single row, as a collection of fields.
The first one is owning, and the second one is not.
* [reflink rows] and [reflink rows_view] represent several rows of the same size.
* There are three methods to read rows:
* [refmem resultset read_one] reads a single row.
* [refmem resultset read_some] reads a batch of an unspecified size.
* [refmem resultset read_all] reads all rows at once.
* Each method provides two overloads:
* An "owning" overload, which populates a [reflink row] or [reflink rows] by lvalue reference.
* A "view" overload (taking a [reflink use_views_t] placeholder), which returns views into internal buffers.
* You can use [refmem resultset complete] to check whether we've read all rows for this resultset or not.
This yields 6 different methods to retrieve rows. The following table shows them all:
[table
[
[Function]
[Typical use]
[Use when...]
]
[
[
[refmem resultset read_one], owning
]
[
```
row r;
while (result.read_one(r))
{
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* [*By default], for resultsets of arbitrary size.
* When processing one row at a time is preferred.
* A good alternative to [refmem resultset read_all], when all rows may not fit in memory.
]
]
[
[
[refmem resultset read_all], owning
]
[
```
rows all_rows;
result.read_all(all_rows);
for (row_view r: all_rows)
{
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* [*By default], for small resultsets.
* When having all rows at the same time in memory is advantageous.
]
]
[
[
[refmem resultset read_some], owning
]
[
```
rows row_batch;
while (!result.complete())
{
result.read_some(row_batch);
for (row_view r: row_batch)
{
// Process r as required
std::cout << r << std::endl;
}
}
```
]
[
* When efficiency is key.
* You can't use the non-owning version of `read_some` because of view validity issues
(more common in its async form).
]
]
[
[
[refmem resultset read_one], non-owning
]
[
```
while (true)
{
row_view r = result.read_one(use_views);
if (result.complete())
break;
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* When efficiency is important.
* When processing one row at a time is preferred.
* There will be no server interaction that may invalidate the view while you process it.
]
]
[
[
[refmem resultset read_all], non-owning
]
[
```
rows_view all_rows = result.read_all(use_views);
for (row_view r: all_rows)
{
// Process r as required
std::cout << r << std::endl;
}
```
]
[
* When efficiency is important.
* The resultset is small, as it must fit in memory.
* Having all rows at the same time in memory is advantageous.
* There will be no server interaction that may invalidate the view while you process it.
]
]
[
[
[refmem resultset read_some], non-owning
]
[
```
while (!result.complete())
{
rows_view row_batch = result.read_some(use_views);
for (row_view r: row_batch)
{
// Process r as required
std::cout << r << std::endl;
}
}
```
]
[
* [*By default], when efficiency is key.
* There will be no server interaction that may invalidate the view while you process it.
]
]
]
Some additional guidelines to help you choose between methods:
* If you don't need maximum efficiency, and your resultset is small,
use the [*owning] version of `read_all`. Note that this doesn't mean
`read_all` is inefficient, as all functions have been optimized for maximum memory re-use.
* If you don't need maximum efficiency, and your resultset is really big (e.g. all rows wouldn't fit in memory),
use the [*owning] version of `read_one`.
* If your resultset contains very, very long strings, use any of the [*non-owning] functions, which
avoid some copying.
* If you need maximum efficiency, use the [*non-owning] version of `read_some`.
* If you need to keep your data valid between operations (e.g. you get a batch of rows,
and send them through an HTTP link while you get the next batch of rows), use any of the [*owning] methods.
* Measure before making efficiency-related decisions, and try to keep it simple!
[heading Lifetimes]
If you read with an "owning" function, you get a [reflink row] or [reflink rows] object.
These classes have value semantics, and are guaranteed to be valid even after the resultset
that read them is destroyed.
Any view type you obtain from a `row` or `rows` is valid until the owning object is destroyed
or assigned to (similarly to how references to elements in a `vector` work):
```
rows all_rows;
// Populate it
tcp_ssl_resultset result;
conn.query("SELECT 1", result);
result.read_all(all_rows);
// rv references all_rows; valid until all_rows is destroyed
row_view rv = all_rows.at(0);
// fv references all_rows, too; same ownership rules
field_view fv = rv[0];
// Replace the original all_rows object.
// Views referencing all_rows are invalidated
conn.query("SELECT 1", result);
result.read_all(all_rows);
// Do NOT use rv or fv here - dangling views
```
Note that both `row` and `rows` [*are immutable types] (in that they don't have mutating functions other than assignment).
They are designed to maximize memory re-use when reading rows. If you need to mutate a row, you can use [refmem row as_vector]
or [refmem row_view as_vector] to obtain a `std::vector<field>`.
If you read with a "non-owning" function, the returned view points into the underlying
`connection` internal read buffer. This means that [*any operation implying a stream read]
on the `connection` or associated `statement` and `resultset` objects invalidates the view.
For example:
```
tcp_ssl_resultset result1, result2;
// Issue the first query
conn.query("SELECT 1", result1);
// Get all the rows, as a view. all_rows points into
// conn's internal buffers
rows_view all_rows = result1.read_all(use_views);
// Issue the second query. A query implies a read operation
// on the underlying stream, so this line
// INVALIDATES all_rows!
conn.query("SELECT 2", result2);
// Do NOT use all_rows here - dangling view!!
```
[heading:read_some More on read_some]
To properly understand `read_one`, we need to know that every [reflink connection]
owns an internal *read buffer*, where packets sent by the server are stored.
It is a single, flat buffer, and you can configure its initial size using
[refmem handshake_params buffer_config] when establishing the connection.
The read buffer may be grown under certain circumstances to accomodate large messages.
`read_some` gets the maximum number of rows that fit in the read buffer (without growing it)
performing a single `read_some` operation on the stream (or using cached data).
If there are rows to read, `read_some` guarantees to read at least one. This means that,
if doing what we described yields no rows (e.g. because of a large row that doesn't fit
into the read buffer), `read_one` will grow the buffer or perform more reads until at least
one row has been read.
If you want to get the most of `read_some`, customize the initial read buffer size
to maximize the number of rows that each batch retrieves.
[endsect]
[section:complete Resultsets becoming complete]
When you have read every single row in a [reflink resultset],
then we say the resultset is [*complete]. You can query for this
fact calling [refmem resultset complete].
If a resultset comes from a SQL statement that generates rows
(e.g. a `SELECT` statement that matches some rows), it completes
the first time you try to read a row, but there are not any more available.
For example, in a resultset with 4 rows, any of the following actions will
complete the resultset:
* Calling [refmem resultset read_one] 5 times.
* Calling [refmem resultset read_all].
If the SQL statement did not generate any rows, we say that the resultset
is [*empty]. This happens for `UPDATE` or `INSERT` statements. Empty resultsets
are complete from the beginning: you don't need to
call [refmem resultset read_one] to make them complete.
After a [reflink resultset] is complete, some extra information about
the query becomes available, like [refmem resultset warning_count]
or [refmem resultset affected_rows]. MySQL sends this information
as an extra packet only after sending every single resultset row,
hence this mechanic.
Calling any row-reading function on a complete resultset is well defined
and has the expected effects.
[endsect]
[section:multifunction Multi-function operations]
Resultsets are generated by multi-function operations like [refmem connection query]
or [refmem statement execute]. These operations will send the execution request to the server.
The server then sends an initial response and all the generated rows.
`query()` and `execute()` will just read the initial response, and not the rows.
This means that the rows will be waiting in the client's read buffer, and must be read
before engaging in further operations. Once you get a `resultset`,
[*you must read all the generated rows], until [refmem resultset complete]
returns `true`. If you start any other operation involving a read before doing this,
you will get packet mismatches and undefined results.
[endsect]
[section:metadata Metadata]
Resultset objects hold metadata describing the columns they
contain. You can access these data using [refmem resultset meta].
This function returns a collection of [reflink metadata]
objects. There is one object for each column retrieved by the SQL query,
and in the same order as in the query. You can find a bunch
of useful information in this object, like the column name,
its type, whether it's a key or not, and so on.
Metadata is always available (i.e. you don't need [refmem resultset complete]
to return `true` before accessing it). For empty resultsets, [refmem resultset meta]
returns an empty collection.
[endsect]
[endsect]

View File

@ -10,7 +10,7 @@
[nochunk]
This section delves deeper on how to use [reflink field] and [reflink field_view]
and its underlying types. Please make sure you've read [link mysql.overview.rows_fields.fields this section]
and its underlying types. Please make sure you've read [link mysql.overview.fields this section]
before going on.
[section field_view vs field]
@ -22,20 +22,20 @@ and [reflink field], which is owning. The relationship between them is similar t
For efficiency reasons, all library functions return `field_view`s. For example:
```
row r;
// read it using resultset::read_one or a similar function
field_view fv = r.at(0); // fv doesn't own its memory; if r goes out of scope, fv becomes invalid
boost::string_view sv = fv.as_string(); // sv also points into r; if r goes out of scope, sv becomes invalid
resultset result;
conn.query("SELECT 'Hello world!'", result);
field_view fv = result.rows().at(0).at(0); // fv doesn't own its memory; if result goes out of scope, fv becomes invalid
boost::string_view sv = fv.as_string(); // sv also points into result; if result goes out of scope, sv becomes invalid
```
When dealing with scalars (anything that is neither a string nor a blob), `field_view`'s accessors make
a copy of the scalar:
```
row r;
// read it using resultset::read_one or a similar function
field_view fv = r.at(0); // fv doesn't own its memory; if r goes out of scope, fv becomes invalid
std::int64_t intv = fv.as_int64(); // intv is valid even after r goes out of scope
resultset result;
conn.query("SELECT 42", result);
field_view fv = result.rows().at(0).at(0); // fv doesn't own its memory; if result goes out of scope, fv becomes invalid
std::int64_t intv = fv.as_int64(); // intv is valid even after result goes out of scope
```
`field_view`s are cheap to create and to copy, as they are small objects and don't perform
@ -45,10 +45,10 @@ expensive to create and copy, as they may perform memory allocations.
You may create a `field` from a `field_view`, taking ownership of its contents:
```
row r;
// read it using resultset::read_one or a similar function
field_view fv = r.at(0); // fv doesn't own its memory; if r goes out of scope, fv becomes invalid
field f (fv); // f takes ownership of fv's contents. f is valid even after r goes out of scope
resultset result;
conn.query("SELECT 'Hello world!'", result);
field_view fv = result.rows().at(0).at(0); // fv doesn't own its memory; if result goes out of scope, fv becomes invalid
field f (fv); // f takes ownership of fv's contents. f is valid even after result goes out of scope
```
`field` and `field_view` use the same underlying types for scalars. For strings and blobs,

View File

@ -23,8 +23,8 @@ type always has one of the two folling forms:
# `void(error_code)`. Used in operations that do
not have a proper result, e.g. [refmem connection async_connect].
# `void(error_code, T)`. Used in operations that
have a result, e.g. [refmem resultset async_read_one]
(in this case, `T` is `bool` for the "owning" version).
have a result, e.g. [refmem connection async_read_one_row]
(in this case, `T` is `row_view`).
As noted [link mysql.error_handling here], all asynchronous
are overloaded to accept an optional [reflink error_info]
@ -38,40 +38,11 @@ As mentioned in [link mysql.overview.async this section], only a single read and
write operation per connection can be outstanding at a given point in time.
If you need to perform queries in parallel, open more connections to the server.
[endsect]
[section:proxy_io Proxy I/O objects]
[reflink resultset] and [reflink statement] are proxy I/O objects. This means
that they don't own a separate `Stream` object, but rather rely on an underlying
[reflink connection] to perform I/O. This has the following implications:
* To use a `resultset` or a `statement` for operations that involve I/O, the
`connection` object that created them must be alive and open.
* Read and write operations on these objects count towards the limit of
one concurrent read and write operation per connection.
* I/O operations on these objects end up mutating the underlying `connection`'s
state. In a multi-threaded environment, appropriate guards must be in place
to avoid race conditions. This applies among all proxy I/O objects for a single connection;
different connections share no state.
Recall also that [reflink statement] objects are proxy I/O objects. All I/O operations
on statements end up using the underlying connection's stream.
[endsect]
[section:ops Protocol operations]
The MySQL protocol is a half-duplex request/reply protocol that has the concept of "operations".
This library models some operations as single functions (e.g. [refmem connection prepare_statement]),
and splits other operations into several calls (e.g. [refmem connection query] +
[refmem resultset read_all]), to provide more flexibility.
Once you engage in a multi-function operation, you must
complete it (e.g. by calling `resultset::read_xxx` until all rows have been read) before engaging
into the next one. Failing to do so will produce network packet mismatches, resulting in
undefined behavior.
[endsect]
[section:completion_tokens Completion tokens]
Any completion token you may use with Boost.Asio can also be used
@ -83,7 +54,7 @@ with this library. Here are some of the most common:
will be called when the operation completes. The initiating
function will return `void`.
[link mysql.examples.query_async_callbacks This example]
[link mysql.examples.async_callbacks This example]
demonstrates asynchronous text queries with callbacks.
* [*Futures]. In this case, you pass in the constant
[asioreflink use_future use_future] as completion token.
@ -98,7 +69,7 @@ with this library. Here are some of the most common:
Note that the exception will [*not] contain the extra information
stored in the [reflink error_info].
[link mysql.examples.query_async_futures This example]
[link mysql.examples.async_futures This example]
demonstrates using futures with async queries.
* [*__Coroutine__ coroutines]. In this case, you pass in
a [asioreflink yield_context yield_context]. To obtain one
@ -116,7 +87,7 @@ with this library. Here are some of the most common:
will not contain the extra information stored in the
[reflink error_info].
[link mysql.examples.query_async_coroutines This example]
[link mysql.examples.async_coroutines This example]
uses __Coroutine__ coroutines with async queries.
* [*C++20 coroutines]. In this case, you pass in the constant
[asioreflink use_awaitable use_awaitable] as completion token.
@ -131,7 +102,7 @@ with this library. Here are some of the most common:
Note that this exception will not contain the extra
information stored in the [reflink error_info].
[link mysql.examples.query_async_coroutinescpp20 This example]
[link mysql.examples.async_coroutinescpp20 This example]
demonstrates using C++20 coroutines to perform text
queries.
* Any other type that satisfies the __CompletionToken__ type requirements.

View File

@ -7,9 +7,8 @@
[section:other_streams UNIX sockets and other stream types]
All I/O objects ([reflink connection], [reflink statement]
and [reflink resultset]) are templatized on the stream type.
Any object fulfilling the __Stream__ concept may be used
I/O objects ([reflink connection] and [reflink statement])
are templatized on the stream type. Any object fulfilling the __Stream__ concept may be used
as template argument.
[heading Convenience type aliases]
@ -26,44 +25,32 @@ This library provides helper type aliases for the most common cases:
[SSL over TCP]
[`boost::asio::ssl::stream<boost::asio::ip::tcp::socket>`]
[
[reflink tcp_ssl_connection]
[reflink tcp_ssl_connection][br]
[reflink tcp_ssl_statement]
[reflink tcp_ssl_resultset]
]
]
[
[Plaintext TCP]
[`boost::asio::ip::tcp::socket`]
[
[reflink tcp_connection]
[reflink tcp_connection][br]
[reflink tcp_statement]
[reflink tcp_resultset]
]
]
[
[SSL over UNIX sockets]
[`boost::asio::ssl::stream<boost::asio::local::stream_protocol::socket>`]
[
[reflink unix_ssl_connection]
[reflink unix_ssl_connection][br]
[reflink unix_ssl_statement]
[reflink unix_ssl_resultset]
]
]
[
[Plaintext UNIX sockets]
[`boost::asio::local::stream_protocol::socket`]
[
[reflink unix_connection]
[reflink unix_connection][br]
[reflink unix_statement]
[reflink unix_resultset]
]
]
]
@ -75,7 +62,7 @@ Note that the UNIX socket type aliases only exist when the macro
[link mysql.examples.unix_socket This example] employs a UNIX
domain socket to establish a connection to a MySQL server.
[heading:connection Streams that are not sockets]
[heading:non_sockets Streams that are not sockets]
When the `Stream` template argument for your `connection` fulfills
the __SocketStream__ type requirements, you can use the member functions

View File

@ -8,7 +8,7 @@
[section:error_handling Error handling and available overloads]
This section describes the different error handling strategies
you may use with __Self__, as well as the different overloads
you may use with this library, as well as the different overloads
available for each function involving network transfers.
The different overloads differ in how they deal with errors.
@ -38,23 +38,14 @@ There are two overloads for each synchronous network function:
[heading Asynchronous functions]
The associated handler signature of all asynchronous functions
has one of the following two forms:
There are two overloads for each asynchronous network function:
* `void(error_code)`. Used in operations that do
not have a proper result, e.g. [refmem connection async_connect].
* `void(error_code, T)`. Used in operations that
have a result, e.g. [refmem resultset async_read_one].
* Without `error_info`, having the `CompletionToken` as last parameter.
When they fail, they call the completion handler with a non-empty `error_code`.
* With `error_info`, having an `error_info&` and `CompletionToken` as the last
two parameters. When they fail, they set the `error_info` parameter to any server-provided
diagnostic information, if available, and then call the completion handler with a non-empty `error_code`.
When asynchronous operations fail, they communicate it
by calling the handler with a non-zero [reflink error_code].
If you are interested in also obtaining an [reflink error_info]
when using asynchronous functions, there is an extra overload
of each asynchronous function taking an additional output lvalue reference
[reflink error_info] parameter. This parameter is set before
calling the handler.
[heading Server-side and client-side error codes]
__Self__ [reflink error_code]s use [reflink errc] as the

View File

@ -101,8 +101,8 @@ underlying stream does not support SSL.
[refmem handshake_params buffer_config], of type [reflink buffer_params], lets you
specify some defaults for internal buffer sizes. You can currently set the initial
size of the read buffer, which can impact the performance of [refmem resultset read_some],
as explained [link mysql.resultsets.read.read_some in this section].
size of the read buffer, which can impact the performance of [refmem connection read_some_rows],
as explained [link mysql.multi_function.read_some_rows in this section].
[endsect]

View File

@ -47,7 +47,7 @@ character set and collation. You can specify them in two ways:
You can use [refmem connection query] to issue the statement:
```
tcp_ssl_resultset result;
resultset result;
conn.query("SET NAMES utf8mb4", result);
// Further operations can assume utf8mb4 as conn's charset
```

View File

@ -13,15 +13,15 @@ setup] first.
Here is a list of available examples:
# [link mysql.examples.query_sync Text query, synchronous]
# [link mysql.examples.text_queries Text queries]
# [link mysql.examples.prepared_statements Prepared statements]
# [link mysql.examples.metadata Metadata]
# [link mysql.examples.unix_socket UNIX sockets]
# [link mysql.examples.query_async_callbacks Text query, async with callbacks]
# [link mysql.examples.query_async_futures Text query, async with futures]
# [link mysql.examples.query_async_coroutines Text query, async with Boost.Coroutine coroutines]
# [link mysql.examples.query_async_coroutinescpp20 Text query, async with C++20 coroutines]
# [link mysql.examples.default_completion_tokens Default completion tokens]
# [link mysql.examples.async_callbacks Async functions using callbacks]
# [link mysql.examples.async_futures Async functions using futures]
# [link mysql.examples.async_coroutines Async functions using Boost.Coroutine]
# [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.ssl Setting SSL options]
@ -48,27 +48,26 @@ which already ships with all the required configuration:
Please note that this container is just for demonstrative purposes,
and is not suitable for production.
The root MySQL user for these containers is `root` and it has an empty password.
[endsect]
[section:query_sync Text query, synchronous]
[section:text_queries Text queries]
This example demonstrates how to connect a [reflink connection],
how to issue a [link mysql.queries text query], and how to
[link mysql.resultsets read a resultset]. It employs synchronous
functions with exceptions as error handling. __see_error_handling__
This example demonstrates how to issue text queries, without user-supplied parameters.
It employs synchronous functions with exceptions as error handling. __see_error_handling__
__assume_setup__
[import ../../example/query_sync.cpp]
[example_query_sync]
[import ../../example/text_queries.cpp]
[example_text_queries]
[endsect]
[section:prepared_statements Prepared statements]
This example demonstrates how to use [link mysql.prepared_statements
prepared statements]. It employs synchronous functions with
exceptions as error handling. __see_error_handling__
This example demonstrates how to use prepared statements.
It employs synchronous functions with exceptions as error handling. __see_error_handling__
__assume_setup__
@ -79,10 +78,8 @@ __assume_setup__
[section:metadata Metadata]
This example demonstrates how to use the available
[link mysql.resultsets.metadata metadata] in a [reflink resultset].
It employs synchronous functions with
exceptions as error handling. __see_error_handling__
This example demonstrates how to use the available metadata in a [reflink resultset].
It employs synchronous functions with exceptions as error handling. __see_error_handling__
__assume_setup__
@ -109,74 +106,61 @@ __assume_setup__
[endsect]
[section:query_async_callbacks Text query, async with callbacks]
[section:async_callbacks Async functions using callbacks]
This example demonstrates how to connect a [reflink connection],
how to issue a [link mysql.queries text query], and how to
[link mysql.resultsets read a resultset] using
[link mysql.async asynchronous functions] with callbacks.
This example demonstrates how use the asynchronous functions using callbacks.
__assume_setup__
[import ../../example/query_async_callbacks.cpp]
[example_query_async_callbacks]
[import ../../example/async_callbacks.cpp]
[example_async_callbacks]
[endsect]
[section:query_async_futures Text query, async with futures]
[section:async_futures Async functions using futures]
This example demonstrates how to connect a [reflink connection],
how to issue a [link mysql.queries text query], and how to
[link mysql.resultsets read a resultset] using
[link mysql.async asynchronous functions] with futures
([asioreflink use_future use_future]).
This example demonstrates how use the asynchronous functions using futures.
__assume_setup__
[import ../../example/query_async_futures.cpp]
[example_query_async_futures]
[import ../../example/async_futures.cpp]
[example_async_futures]
[endsect]
[section:query_async_coroutines Text query, async with Boost.Coroutine coroutines]
[section:async_coroutines Async functions using Boost.Coroutine]
This example demonstrates how to connect a [reflink connection],
how to issue a [link mysql.queries text query], and how to
[link mysql.resultsets read a resultset] using
[link mysql.async asynchronous functions] with __Coroutine__ coroutines
This example demonstrates how use the asynchronous functions using __Coroutine__
(using [asioreflink yield_context yield_context] and
[asioreflink spawn spawn]).
__assume_setup__
[import ../../example/query_async_coroutines.cpp]
[example_query_async_coroutines]
[import ../../example/async_coroutines.cpp]
[example_async_coroutines]
[endsect]
[section:query_async_coroutinescpp20 Text query, async with C++20 coroutines]
[section:async_coroutinescpp20 Async functions using C++20 coroutines]
This example demonstrates how to connect a [reflink connection],
how to issue a [link mysql.queries text query], and how to
[link mysql.resultsets read a resultset] using
[link mysql.async asynchronous functions] with C++20 coroutines
([asioreflink use_awaitable use_awaitable] and [asioreflink
This example demonstrates how use the asynchronous functions using C++20 coroutines
(using [asioreflink use_awaitable use_awaitable] and [asioreflink
co_spawn co_spawn]).
__assume_setup__
[import ../../example/query_async_coroutinescpp20.cpp]
[example_query_async_coroutinescpp20]
[import ../../example/async_coroutinescpp20.cpp]
[example_async_coroutinescpp20]
[endsect]
[section:default_completion_tokens Default completion tokens]
[section:default_completion_tokens Async functions using default completion tokens]
This example demonstrates how to use Boost.Asio's
default completion token functionality with __Self__.
For that purpose, it employs C++20 coroutines.
If you are not familiar with them, look at
[link mysql.examples.query_async_coroutinescpp20 this example]
[link mysql.examples.async_coroutinescpp20 this example]
first.
__assume_setup__
@ -193,7 +177,7 @@ 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]
[link mysql.examples.async_coroutinescpp20 this example]
first.
__assume_setup__
@ -213,7 +197,7 @@ exceptions as error handling. __see_error_handling__
__assume_setup__ Additionally, you should run your MySQL server
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`.
You can find them in this project's GitHub repository, under `tools/ssl`.
If you're using the docker container, the setup has already been done
for you.

View File

@ -53,7 +53,7 @@ Next, define the following environment variables:
* 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.
`boost/mysql/test/integration//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`.

View File

@ -1,11 +0,0 @@
[/
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)
]
Query strings are assumed to be encoded using the connection's
character set. You can set this value when establishing the
connection (see [link mysql.connparams.collation this section])
or change it using a __SET_NAMES__ statement.

View File

@ -20,8 +20,8 @@
<member><link linkend="mysql.ref.boost__mysql__connection">connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__date">date</link></member>
<member><link linkend="mysql.ref.boost__mysql__datetime">datetime</link></member>
<member><link linkend="mysql.ref.boost__mysql__execute_options">execute_options</link></member>
<member><link linkend="mysql.ref.boost__mysql__error_info">error_info</link></member>
<member><link linkend="mysql.ref.boost__mysql__execution_state">execution_state</link></member>
<member><link linkend="mysql.ref.boost__mysql__field">field</link></member>
<member><link linkend="mysql.ref.boost__mysql__field_view">field_view</link></member>
<member><link linkend="mysql.ref.boost__mysql__handshake_params">handshake_params</link></member>
@ -33,6 +33,7 @@
<member><link linkend="mysql.ref.boost__mysql__rows">rows</link></member>
<member><link linkend="mysql.ref.boost__mysql__rows_view">rows_view</link></member>
<member><link linkend="mysql.ref.boost__mysql__statement">statement</link></member>
<member><link linkend="mysql.ref.boost__mysql__statement_base">statement_base</link></member>
</simplelist>
</entry>
<entry valign="top">
@ -44,16 +45,12 @@
<member><link linkend="mysql.ref.boost__mysql__time">time</link></member>
<member><link linkend="mysql.ref.boost__mysql__error_code">error_code</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_connection">tcp_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_resultset">tcp_resultset</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_statement">tcp_statement</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_ssl_connection">tcp_ssl_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_ssl_resultset">tcp_ssl_resultset</link></member>
<member><link linkend="mysql.ref.boost__mysql__tcp_ssl_statement">tcp_ssl_statement</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_connection">unix_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_resultset">unix_resultset</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_statement">unix_statement</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_ssl_connection">unix_ssl_connection</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_ssl_resultset">unix_ssl_resultset</link></member>
<member><link linkend="mysql.ref.boost__mysql__unix_ssl_statement">unix_ssl_statement</link></member>
</simplelist>
</entry>
@ -76,7 +73,6 @@
<member><link linkend="mysql.ref.boost__mysql__min_datetime">min_datetime</link></member>
<member><link linkend="mysql.ref.boost__mysql__max_time">max_time</link></member>
<member><link linkend="mysql.ref.boost__mysql__min_time">min_time</link></member>
<member><link linkend="mysql.ref.boost__mysql__no_statement_params">no_statement_params</link></member>
</simplelist>
</entry>
<entry valign="top">

View File

@ -65,15 +65,14 @@ endif()
# Build and run all the examples
add_example(tutorial TRUE ${SERVER_HOST})
add_example(field 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(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})
if ("$ENV{BOOST_MYSQL_NO_UNIX_SOCKET_TESTS}" STREQUAL "")

View File

@ -17,15 +17,14 @@ if $(hostname) = ""
# Example list
local examples_no_unix =
tutorial
field
query_sync
query_async_callbacks
query_async_coroutines
query_async_coroutinescpp20
query_async_futures
metadata
text_queries
prepared_statements
async_callbacks
async_coroutines
async_coroutinescpp20
async_futures
default_completion_tokens
metadata
ssl
timeouts
;
@ -39,7 +38,7 @@ for local example in $(examples_no_unix)
unit-test $(example_name)
:
"$(example).cpp"
/boost/mysql//mysql
/boost/mysql/test//mysql
/boost/coroutine//boost_coroutine
:
<testing.arg>"example_user example_password $(hostname)"
@ -53,7 +52,7 @@ if [ os.environ BOOST_MYSQL_NO_UNIX_SOCKET_TESTS ] = "" {
unit-test boost_mysql_example_unix_socket
:
unix_socket.cpp
/boost/mysql//mysql
/boost/mysql/test//mysql
:
<testing.arg>"example_user example_password"
;

View File

@ -5,12 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[example_query_async_callbacks
//[example_async_callbacks
#include <boost/mysql.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/io_context.hpp>
@ -31,9 +28,9 @@ using boost::mysql::error_code;
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
void die_on_error(
@ -56,16 +53,18 @@ class application
boost::asio::ip::tcp::resolver resolver; // To perform hostname resolution
boost::asio::ssl::context ssl_ctx; // MySQL 8+ default settings require SSL
boost::mysql::tcp_ssl_connection conn; // Represents the connection to the MySQL server
boost::mysql::tcp_ssl_resultset resultset; // A result from a query
boost::mysql::rows rows; // The rows to be read from the resultset
boost::mysql::tcp_ssl_statement stmt; // A prepared statement
boost::mysql::resultset result; // A result from a query
boost::mysql::error_info
additional_info; // Will be populated with additional information about any errors
additional_info; // Will be populated with additional information about any errors
const char* company_id; // The ID of the company whose employees we want to list. Untrusted.
public:
application(const char* username, const char* password)
application(const char* username, const char* password, const char* company_id)
: conn_params(username, password, "boost_mysql_examples"),
resolver(ctx.get_executor()),
ssl_ctx(boost::asio::ssl::context::tls_client),
conn(ctx, ssl_ctx)
conn(ctx, ssl_ctx),
company_id(company_id)
{
}
@ -88,49 +87,40 @@ public:
{
conn.async_connect(*eps.begin(), conn_params, additional_info, [this](error_code err) {
die_on_error(err, additional_info);
query_employees();
prepare_statement();
});
}
void prepare_statement()
{
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt,
additional_info,
[this](error_code err) {
die_on_error(err, additional_info);
query_employees();
}
);
}
void query_employees()
{
const char*
sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
conn.async_query(sql, resultset, additional_info, [this](error_code err) {
die_on_error(err, additional_info);
resultset.async_read_all(rows, additional_info, [this](error_code err) {
stmt.async_execute(
std::make_tuple(company_id),
result,
additional_info,
[this](error_code err) {
die_on_error(err, additional_info);
for (const auto& employee : rows)
for (boost::mysql::row_view employee : result.rows())
{
print_employee(employee);
}
update_slacker();
});
});
}
void update_slacker()
{
const char* sql = "UPDATE employee SET salary = 15000 WHERE last_name = 'Slacker'";
conn.async_query(sql, resultset, additional_info, [this](error_code err) {
die_on_error(err, additional_info);
ASSERT(resultset.complete()); // an UPDATE never returns rows
query_intern();
});
}
void query_intern()
{
const char* sql = "SELECT salary FROM employee WHERE last_name = 'Slacker'";
conn.async_query(sql, resultset, additional_info, [this](error_code err) {
die_on_error(err, additional_info);
resultset.async_read_all(rows, additional_info, [this](error_code err) {
die_on_error(err, additional_info);
double salary = rows.at(0).at(0).as_double();
ASSERT(salary == 15000);
close();
});
});
}
);
}
void close()
@ -146,13 +136,18 @@ public:
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
application app(argv[1], argv[2]);
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
application app(argv[1], argv[2], company_id);
app.start(argv[3]); // starts the async chain
app.run(); // run the asio::io_context until the async chain finishes
}

View File

@ -5,10 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[example_query_async_coroutines
//[example_async_coroutines
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
@ -23,9 +22,9 @@ using boost::mysql::error_info;
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
// Throws an exception if an operation failed
@ -37,14 +36,19 @@ void check_error(const error_code& err, const error_info& info = {})
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
const char* hostname = argv[3];
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
// 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);
@ -70,7 +74,7 @@ void main_impl(int argc, char** argv)
*/
boost::asio::spawn(
ctx.get_executor(),
[&conn, &resolver, params, hostname](boost::asio::yield_context yield) {
[&conn, &resolver, params, hostname, company_id](boost::asio::yield_context yield) {
// This error_code and error_info will be filled if an
// operation fails. We will check them for every operation we perform.
boost::mysql::error_code ec;
@ -88,26 +92,26 @@ void main_impl(int argc, char** argv)
conn.async_connect(*endpoints.begin(), params, additional_info, yield[ec]);
check_error(ec);
// Issue the query to the server
const char*
sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_ssl_resultset result;
conn.async_query(sql, result, additional_info, yield[ec]);
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
boost::mysql::tcp_ssl_statement stmt;
conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt,
additional_info,
yield[ec]
);
check_error(ec, additional_info);
/**
* Get all rows in the resultset. We will employ resultset::async_read_one(),
* which reads a single row at every call. resultset::complete() returns true only after
* we've read the last row in the resultset.
*/
boost::mysql::row row;
while (true)
// Execute the statement
boost::mysql::resultset result;
stmt.async_execute(std::make_tuple(company_id), result, additional_info, yield[ec]);
check_error(ec, additional_info);
// Print the employees
for (boost::mysql::row_view employee : result.rows())
{
result.async_read_one(row, additional_info, yield[ec]);
check_error(ec, additional_info);
if (result.complete())
break;
print_employee(row);
print_employee(employee);
}
// Notify the MySQL server we want to quit, then close the underlying connection.

View File

@ -5,11 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[example_query_async_coroutinescpp20
//[example_async_coroutinescpp20
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/tcp_ssl.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
@ -27,11 +25,13 @@ using boost::mysql::error_code;
#ifdef BOOST_ASIO_HAS_CO_AWAIT
using boost::asio::use_awaitable;
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
/**
@ -46,7 +46,7 @@ void print_employee(boost::mysql::row_view employee)
* all information it needs for resuming. When the asynchronous operation completes,
* the coroutine will resume in the point it was left.
*
* The return type of an asynchronous operation that uses boost::asio::use_awaitable
* The return type of an asynchronous operation that uses use_awaitable
* as completion token is a boost::asio::awaitable<T>, where T
* is the second argument to the handler signature for the asynchronous operation.
* If any of the asynchronous operations fail, an exception will be raised
@ -56,7 +56,8 @@ boost::asio::awaitable<void> start_query(
boost::mysql::tcp_ssl_connection& conn,
boost::asio::ip::tcp::resolver& resolver,
const boost::mysql::handshake_params& params,
const char* hostname
const char* hostname,
const char* company_id
)
{
try
@ -65,34 +66,33 @@ boost::asio::awaitable<void> start_query(
auto endpoints = co_await resolver.async_resolve(
hostname,
boost::mysql::default_port_string,
boost::asio::use_awaitable
use_awaitable
);
// Connect to server
co_await conn.async_connect(*endpoints.begin(), params, boost::asio::use_awaitable);
co_await conn.async_connect(*endpoints.begin(), params, use_awaitable);
/**
* Issue the query to the server. Note that async_query returns a
* boost::asio::awaitable<boost::mysql::tcp_ssl_resultset>.
*/
const char*
sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_ssl_resultset result;
co_await conn.async_query(sql, result, boost::asio::use_awaitable);
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
boost::mysql::tcp_ssl_statement stmt;
co_await conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt,
use_awaitable
);
/**
* Get all rows in the resultset. We will employ resultset::async_read_one(),
* which reads a single row at every call. resultset::complete() returns true only after
* we've read the last row in the resultset.
*/
boost::mysql::row row;
while (co_await result.async_read_one(row, boost::asio::use_awaitable))
// Execute the statement
boost::mysql::resultset result;
co_await stmt.async_execute(std::make_tuple(company_id), result, use_awaitable);
// Print all employees
for (boost::mysql::row_view employee : result.rows())
{
print_employee(row);
print_employee(employee);
}
// Notify the MySQL server we want to quit, then close the underlying connection.
co_await conn.async_close(boost::asio::use_awaitable);
co_await conn.async_close(use_awaitable);
}
catch (const boost::system::system_error& err)
{
@ -106,14 +106,19 @@ boost::asio::awaitable<void> start_query(
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
const char* hostname = argv[3];
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
// 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);
@ -136,13 +141,13 @@ void main_impl(int argc, char** argv)
*/
boost::asio::co_spawn(
ctx.get_executor(),
[&conn, &resolver, params, hostname] {
return start_query(conn, resolver, params, hostname);
[&conn, &resolver, params, hostname, company_id] {
return start_query(conn, resolver, params, hostname, company_id);
},
boost::asio::detached
);
// Calling run will actually start the requested operations.
// Calling run will execute the requested operations.
ctx.run();
}

View File

@ -5,10 +5,13 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[example_query_async_futures
//[example_async_futures
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/tcp_ssl.hpp>
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
@ -23,9 +26,9 @@ using boost::mysql::error_code;
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
/**
@ -58,12 +61,17 @@ public:
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
// Context and connections
application app; // boost::asio::io_context and a thread that calls run()
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
@ -96,21 +104,25 @@ void main_impl(int argc, char** argv)
std::future<void> fut = conn.async_connect(*endpoints.begin(), params, use_future);
fut.get();
// Issue the query to the server
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_ssl_resultset result;
fut = conn.async_query(sql, result, use_future);
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
boost::mysql::tcp_ssl_statement stmt;
fut = conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt,
use_future
);
fut.get();
/**
* Get all rows in the resultset. We will employ resultset::async_read_one(),
* which reads a single row at every call. resultset::complete() returns true only after we've
* read the last row in the resultset.
*/
boost::mysql::row row;
while (result.async_read_one(row, use_future).get())
// Execute the statement
boost::mysql::resultset result;
fut = stmt.async_execute(std::make_tuple(company_id), result, use_future);
fut.get();
// Print employees
for (boost::mysql::row_view employee : result.rows())
{
print_employee(row);
print_employee(employee);
}
// Notify the MySQL server we want to quit, then close the underlying connection.

View File

@ -6,9 +6,8 @@
//
//[example_default_completion_tokens]
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/basic_stream_socket.hpp>
@ -29,9 +28,9 @@ using boost::mysql::error_code;
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
/**
@ -68,7 +67,8 @@ boost::asio::awaitable<void, base_executor_type> start_query(
connection_type& conn,
resolver_type& resolver,
const char* hostname,
const boost::mysql::handshake_params& params
const boost::mysql::handshake_params& params,
const char* company_id
)
{
try
@ -82,26 +82,25 @@ boost::asio::awaitable<void, base_executor_type> start_query(
// Connect to server
co_await conn.async_connect(*endpoints.begin(), params);
/**
* Issue the query to the server. Note that the resultset type won't be
* tcp_ssl_resultset, because the stream type we are using is different.
*/
const char*
sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
connection_type::resultset_type result;
co_await conn.async_query(sql, result);
// Prepare an statement. Note that the statement type won't be
// tcp_ssl_statement, because the stream type we are using is different.
// We can use connection::statement_type to help
connection_type::statement_type stmt;
co_await conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt
);
/**
* Get all rows in the resultset. We will employ resultset::async_read_one(),
* which reads a single row at every call and returns true if a row was read successfully.
*/
boost::mysql::row row;
while (co_await result.async_read_one(row))
// Execute it
boost::mysql::resultset result;
co_await stmt.async_execute(std::make_tuple(company_id), result);
for (boost::mysql::row_view employee : result.rows())
{
print_employee(row);
print_employee(employee);
}
// Notify the MySQL server we want to quit, then close the underlying connection.
// This will also deallocate the statement from the server.
co_await conn.async_close();
}
catch (const boost::system::system_error& err)
@ -116,13 +115,15 @@ boost::asio::awaitable<void, base_executor_type> start_query(
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
const char* hostname = argv[3];
const char* company_id = argc == 5 ? argv[4] : "HGS";
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
boost::asio::io_context ctx;
@ -145,8 +146,8 @@ void main_impl(int argc, char** argv)
*/
boost::asio::co_spawn(
ctx.get_executor(),
[&conn, &resolver, hostname, params] {
return start_query(conn, resolver, hostname, params);
[&conn, &resolver, hostname, params, company_id] {
return start_query(conn, resolver, hostname, params, company_id);
},
boost::asio::detached
);

View File

@ -1,50 +0,0 @@
//
// 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)
//
#include <boost/mysql/field_view.hpp>
#include <iostream>
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
void example_as()
{
//[value_example_get
boost::mysql::field_view v("hello"); // v contains type boost::string_view
boost::string_view typed_val = v.as_string(); // retrieves the underlying string
ASSERT(typed_val == "hello");
try
{
v.as_double(); // wrong type! throws boost::mysql::bad_field_access
// v.get_double(); // don't do this - UB
}
catch (const boost::mysql::bad_field_access&)
{
}
//]
}
void example_is()
{
//[value_example_is
boost::mysql::field_view v(std::uint64_t(42)); // v contains type std::uint64_t
ASSERT(v.is_uint64()); // exact type match
ASSERT(!v.is_int64()); // the underlying type is unsigned
ASSERT(!v.is_string());
//]
}
int main()
{
example_as();
example_is();
}

View File

@ -8,8 +8,6 @@
//[example_metadata
#include <boost/mysql.hpp>
#include <boost/mysql/connection.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
@ -56,7 +54,7 @@ void main_impl(int argc, char** argv)
FROM employee emp
JOIN company comp ON (comp.id = emp.company_id)
)";
boost::mysql::tcp_ssl_resultset result;
boost::mysql::resultset result;
conn.query(sql, result);
/**

View File

@ -8,14 +8,13 @@
//[example_prepared_statements
#include <boost/mysql.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>
#include <random>
#include <tuple>
#define ASSERT(expr) \
@ -25,14 +24,27 @@
exit(1); \
}
double generate_random_payrise()
{
std::random_device dev;
std::default_random_engine eng(dev());
std::uniform_real_distribution<> dist(500.0, 3000.0);
return dist(eng);
}
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [employee-first-name]\n";
exit(1);
}
// The first_name of the employee we will be working with. This
// is user-supplied input, and should be treated as untrusted.
const char* first_name = argc == 5 ? argv[4] : "Efficient";
// I/O context and connection. We use SSL because MySQL 8+ default settings require it.
boost::asio::io_context ctx;
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tls_client);
@ -70,66 +82,51 @@ void main_impl(int argc, char** argv)
* We prepare two statements, a SELECT and an UPDATE.
*/
//[prepared_statements_prepare
const char* salary_getter_sql = "SELECT salary FROM employee WHERE first_name = ?";
boost::mysql::tcp_ssl_statement salary_getter;
conn.prepare_statement(salary_getter_sql, salary_getter);
conn.prepare_statement("SELECT salary FROM employee WHERE first_name = ?", salary_getter);
//]
// num_params() returns the number of parameters (question marks)
ASSERT(salary_getter.num_params() == 1);
const char* salary_updater_sql = "UPDATE employee SET salary = ? WHERE first_name = ?";
boost::mysql::tcp_ssl_statement salary_updater;
conn.prepare_statement(salary_updater_sql, salary_updater);
conn.prepare_statement(
"UPDATE employee SET salary = salary + ? WHERE first_name = ?",
salary_updater
);
ASSERT(salary_updater.num_params() == 2);
// TODO: update this
/*
* Once a statement has been prepared, it can be executed as many times as
* desired, by calling statement::execute(). execute takes as input a
* (possibly empty) collection of boost::mysql::value's and returns a resultset (by lvalue
* reference). The returned resultset works the same as the one returned by connection::query().
*
* The parameters passed to execute() are replaced in the order of declaration:
* the first question mark will be replaced by the first passed parameter,
* the second question mark by the second parameter and so on. The number
* of passed parameters must match exactly the number of parameters for
* the prepared statement.
*
* Any collection providing member functions begin() and end() returning
* forward iterators to boost::mysql::field_view's is acceptable. We use
* boost::mysql::make_field_views(), which creates a std::array with the passed in values
* converted to boost::mysql::field_view's.
* desired, by calling statement::execute(). Parameter actual values are provided
* as a std::tuple. Executing a statement yields a resultset.
*/
//[prepared_statements_execute
boost::mysql::tcp_ssl_resultset result;
salary_getter.execute(std::make_tuple("Efficient"), result);
boost::mysql::rows salaries;
result.read_all(salaries); // Get all the results
boost::mysql::resultset select_result, update_result;
salary_getter.execute(std::make_tuple(first_name), select_result);
//]
ASSERT(salaries.size() == 1);
double salary = salaries[0].at(0).as_double(); // First row, first column, cast to double
std::cout << "The salary before the payrise was: " << salary << std::endl;
// First row, first column, cast to double
double old_salary = select_result.rows().at(0).at(0).as_double();
std::cout << "The salary before the payrise was: " << old_salary << std::endl;
// Run the update. In this case, we must pass in two parameters.
salary_updater.execute(std::make_tuple(35000.0, "Efficient"), result);
ASSERT(result.complete()); // an UPDATE never returns rows
double payrise = generate_random_payrise();
salary_updater.execute(std::make_tuple(payrise, first_name), update_result);
ASSERT(update_result.rows().empty()); // an UPDATE never returns rows
/**
* Execute the select again. We can execute a prepared statement
* as many times as we want. We do NOT need to call
* connection::prepare_statement() again.
*/
salary_getter.execute(std::make_tuple("Efficient"), result);
result.read_all(salaries);
salary = salaries.at(0).at(0).as_double();
ASSERT(salary == 35000); // Our update took place, and the dev got his pay rise
std::cout << "The salary after the payrise was: " << salary << std::endl;
salary_getter.execute(std::make_tuple(first_name), select_result);
double new_salary = select_result.rows().at(0).at(0).as_double();
ASSERT(new_salary > old_salary); // Our update took place
std::cout << "The salary after the payrise was: " << new_salary << std::endl;
/**
* Close the statements. Closing a statement deallocates it from the server.
* Once a statement is closed, trying to execute it will return an error.
*
* Closing statements implies communicating with the server and can thus fail.
*

View File

@ -6,8 +6,8 @@
//
//[example_ssl
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
@ -49,9 +49,9 @@ OzBrmpfHEhF6NDU=
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
void main_impl(int argc, char** argv)
@ -108,13 +108,11 @@ void main_impl(int argc, char** argv)
conn.connect(*endpoints.begin(), params);
// We can now use the connection as we would normally do.
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_ssl_resultset result;
const char* sql = "SELECT first_name, last_name, salary FROM employee";
boost::mysql::resultset result;
conn.query(sql, result);
boost::mysql::rows employees;
result.read_all(employees);
for (const auto& employee : employees)
for (auto employee : result.rows())
{
print_employee(employee);
}

View File

@ -5,10 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[example_query_sync
//[example_text_queries
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
@ -31,11 +30,11 @@
* Indexing a row_view yields a boost::mysql::field_view, which is a variant-like
* type representing a single value returned by MySQL.
*/
void print_employee(const boost::mysql::row& employee)
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (type string)
<< employee[1] << "' earns " // last_name (type string)
<< employee[2] << " dollars yearly\n"; // salary (type double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
void main_impl(int argc, char** argv)
@ -78,35 +77,24 @@ void main_impl(int argc, char** argv)
/**
* Before using the connection, we have to connect to the server by:
* - Establishing the TCP-level session.
* - Authenticating to the MySQL server. The SSL handshake is performed as part of this.
* connection::connect takes care of both.
* - Establishing the TCP-level session.
* - Authenticating to the MySQL server. The SSL handshake is performed as part of this.
* connection::connect takes care of both.
*/
conn.connect(*endpoints.begin(), params);
/**
* To issue a SQL query to the database server, use tcp_ssl_connection::query, which takes
* the SQL to be executed as parameter and returns a resultset object by lvalue reference.
*
* Resultset objects represent the result of a query.
* They hold metadata describing the fields the resultset holds (in this case, first_name,
* last_name and salary). Resultsets don't contain the actual data, but have methods to read it.
*
* Resultset objects contain the retrieved rows, among other info.
* We will get all employees working for 'High Growth Startup'.
*/
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::tcp_ssl_resultset result;
boost::mysql::resultset result;
conn.query(sql, result);
/**
* We will use resultset::read_all(), which will read all our rows into
* a boost::mysql::rows object. This is a matrix-like object, specialized for
* MySQL fields. Indexing a rows object returns a row_view, which represents
* an individual row.
*/
boost::mysql::rows all_rows;
result.read_all(all_rows);
for (boost::mysql::row_view employee : all_rows)
// We can access the rows using resultset::rows
for (boost::mysql::row_view employee : result.rows())
{
print_employee(employee);
}
@ -115,16 +103,12 @@ void main_impl(int argc, char** argv)
// resultset will have no fields and no rows
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
conn.query(sql, result);
ASSERT(
result.meta().size() == 0
); // meta() returns a collection containing metadata about the query fields
ASSERT(result.rows().empty()); // UPDATEs don't retrieve rows
// Check we have updated our poor intern salary
conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'", result);
result.read_all(all_rows);
ASSERT(all_rows.size() == 1);
double salary = all_rows[0][0].as_double();
ASSERT(salary == 10000);
double salary = result.rows().at(0).at(0).as_double();
ASSERT(salary == 10000.0);
// Close the connection. This notifies the MySQL we want to log out
// and then closes the underlying socket. This operation implies a network

View File

@ -8,8 +8,6 @@
//[example_timeouts
#include <boost/mysql.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/tcp_ssl.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
@ -37,9 +35,9 @@ constexpr std::chrono::milliseconds TIMEOUT(2000);
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
/**
@ -81,7 +79,8 @@ boost::asio::awaitable<void> start_query(
boost::asio::ip::tcp::resolver& resolver,
boost::asio::steady_timer& timer,
const boost::mysql::handshake_params& params,
const char* hostname
const char* hostname,
const char* company_id
)
{
try
@ -100,26 +99,30 @@ boost::asio::awaitable<void> start_query(
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'";
boost::mysql::tcp_ssl_resultset result;
timer.expires_after(TIMEOUT);
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
boost::mysql::tcp_ssl_statement stmt;
check_timeout(co_await (
timer.async_wait(use_awaitable) || conn.async_query(sql, result, use_awaitable)
timer.async_wait(use_awaitable) ||
conn.async_prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt,
use_awaitable
)
));
// Read all rows
boost::mysql::row row;
while (true)
// Execute the statement
boost::mysql::resultset result;
timer.expires_after(TIMEOUT);
check_timeout(co_await (
timer.async_wait(use_awaitable) ||
stmt.async_execute(std::make_tuple(company_id), result, use_awaitable)
));
// Print all the obtained rows
for (boost::mysql::row_view employee : result.rows())
{
timer.expires_after(TIMEOUT);
check_timeout(co_await (
timer.async_wait(use_awaitable) || result.async_read_one(row, use_awaitable)
));
if (result.complete())
break;
print_employee(row);
print_employee(employee);
}
// Notify the MySQL server we want to quit, then close the underlying connection.
@ -138,14 +141,19 @@ boost::asio::awaitable<void> start_query(
void main_impl(int argc, char** argv)
{
if (argc != 4)
if (argc != 4 && argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <server-hostname>\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> <server-hostname> [company-id]\n";
exit(1);
}
const char* hostname = argv[3];
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
// 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);
@ -162,14 +170,11 @@ void main_impl(int argc, char** argv)
// 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.
*/
// 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);
[&conn, &resolver, &timer, params, hostname, company_id] {
return start_query(conn, resolver, timer, params, hostname, company_id);
},
boost::asio::detached
);

View File

@ -62,22 +62,17 @@ void main_impl(int argc, char** argv)
//[tutorial_query
// Issue the SQL query to the server
const char* sql = "SELECT \"Hello world!\"";
boost::mysql::tcp_ssl_resultset result;
boost::mysql::resultset result;
conn.query(sql, result);
//]
//[tutorial_read
// Read the query results into memory
boost::mysql::rows all_rows;
result.read_all(all_rows);
//]
//[tutorial_fields
//[tutorial_resultset
// Print the first field in the first row
std::cout << all_rows.at(0).at(0) << std::endl;
std::cout << result.rows().at(0).at(0) << std::endl;
//]
//[tutorial_close
// Close the connection
conn.close();
//]
}

View File

@ -14,12 +14,13 @@
#include <boost/system/system_error.hpp>
#include <iostream>
#include <tuple>
void print_employee(boost::mysql::row_view employee)
{
std::cout << "Employee '" << employee[0] << " " // first_name (string)
<< employee[1] << "' earns " // last_name (string)
<< employee[2] << " dollars yearly\n"; // salary (double)
std::cout << "Employee '" << employee.at(0) << " " // first_name (string)
<< employee.at(1) << "' earns " // last_name (string)
<< employee.at(2) << " dollars yearly\n"; // salary (double)
}
#define ASSERT(expr) \
@ -29,7 +30,8 @@ void print_employee(boost::mysql::row_view employee)
exit(1); \
}
/* UNIX sockets are only available in, er, UNIX systems. Typedefs for
/**
* UNIX sockets are only available on, er, UNIX systems. Typedefs for
* UNIX socket-based connections are only available in UNIX systems.
* Check for BOOST_ASIO_HAS_LOCAL_SOCKETS to know if UNIX socket
* typedefs are available in your system.
@ -40,15 +42,16 @@ void main_impl(int argc, char** argv)
{
if (argc != 3 && argc != 4)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> [<socket-path>]\n";
std::cerr << "Usage: " << argv[0]
<< " <username> <password> [<socket-path>] [<company-id>]\n";
exit(1);
}
const char* socket_path = "/var/run/mysqld/mysqld.sock";
if (argc == 4)
{
socket_path = argv[3];
}
const char* socket_path = argc >= 4 ? argv[3] : "/var/run/mysqld/mysqld.sock";
// The company_id whose employees we will be listing. This
// is user-supplied input, and should be treated as untrusted.
const char* company_id = argc == 5 ? argv[4] : "HGS";
/**
* Connection parameters that tell us where and how to connect to the MySQL server.
@ -71,30 +74,24 @@ void main_impl(int argc, char** argv)
boost::mysql::unix_ssl_connection conn(ctx, ssl_ctx);
conn.connect(ep, params); // UNIX socket connect and MySQL handshake
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
boost::mysql::unix_ssl_resultset result;
conn.query(sql, result);
// We will be using company_id, which is untrusted user input, so we will use a prepared
// statement.
boost::mysql::unix_ssl_statement stmt;
conn.prepare_statement(
"SELECT first_name, last_name, salary FROM employee WHERE company_id = ?",
stmt
);
// Get all the rows in the resultset
boost::mysql::rows all_rows;
result.read_all(all_rows);
for (const auto& employee : all_rows)
// Execute the statement
boost::mysql::resultset result;
stmt.execute(std::make_tuple(company_id), result);
// Print employees
for (boost::mysql::row_view employee : result.rows())
{
print_employee(employee);
}
// We can issue any SQL statement, not only SELECTs. In this case, the returned
// resultset will have no fields and no rows
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
conn.query(sql, result);
ASSERT(result.complete()); // UPDATE queries never return rows
// Check we have updated our poor intern salary
conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'", result);
result.read_all(all_rows);
double salary = all_rows.at(0).at(0).as_double();
ASSERT(salary == 10000);
// Notify the MySQL server we want to quit, then close the underlying connection.
conn.close();
}

View File

@ -24,7 +24,7 @@ public:
/**
* \brief Initializing constructor.
* \param init_read_buffer_size Initial size of the read buffer. A bigger read buffer
* can increase the number of rows returned by \ref resultset::read_some.
* can increase the number of rows returned by \ref connection::read_some_rows.
*/
constexpr buffer_params(std::size_t init_read_buffer_size = 0) noexcept
: initial_read_buffer_size_(init_read_buffer_size)

View File

@ -18,7 +18,7 @@ namespace mysql {
* \brief Represents the database type of a MySQL column.
* \details This represents a database type, as opposed to \ref field_kind, which represents a
* C++ type.
*
*\n
* Unless otherwise noted, the names in this enumeration
* directly correspond to the names of the types you would use in
* a `CREATE TABLE` statement to create a column of this type
@ -53,7 +53,6 @@ enum class column_type
};
/**
* \relates column_type
* \brief Streams a `column_type`.
*/
inline std::ostream& operator<<(std::ostream& os, column_type t)

View File

@ -8,13 +8,19 @@
#ifndef BOOST_MYSQL_CONNECTION_HPP
#define BOOST_MYSQL_CONNECTION_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/detail/protocol/protocol_types.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/resultset.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/detail/protocol/protocol_types.hpp>
#include <type_traits>
#include <utility>
@ -33,48 +39,33 @@ namespace mysql {
* `connection` is the main I/O object that this library implements. It owns a `Stream` object that
* is accessed by functions involving network operations, as well as session state. You can access
* the stream using \ref connection::stream, and its executor via \ref connection::get_executor. The
* executor used by this object is always the same as the underlying stream. Other I/O objects
* (`statement` and `resultset`) are proxy I/O objects, which means that they pointing to the stream
* and state owned by `*this`.
*\n
* `connection` is move constructible and move assignable, but not copyable.
* Moved-from connection objects are left in a state that makes them not
* usable for most of the operations. The function \ref connection::valid
* returns whether an object is in a usable state or not. The only allowed
* operations on moved-from connections are:
*\n
* * Destroying them.
* * Participating in other move construction/assignment operations.
* * Calling \ref connection::valid.
*\n
* In particular, it is __not__ allowed to call \ref connection::handshake
* on a moved-from connection in order to re-open it.
* executor used by this object is always the same as the underlying stream.
*/
template <class Stream>
class connection
{
std::unique_ptr<detail::channel<Stream>> channel_;
detail::channel<Stream>& get_channel() noexcept
{
assert(valid());
return *channel_;
}
const detail::channel<Stream>& get_channel() const noexcept
{
assert(valid());
assert(channel_ != nullptr);
return *channel_;
}
error_info& shared_info() noexcept { return get_channel().shared_info(); }
public:
// TODO: hide this
detail::channel<Stream>& get_channel() noexcept
{
assert(channel_ != nullptr);
return *channel_;
}
/**
* \brief Initializing constructor.
* \details
* As part of the initialization, a `Stream` object is created
* by forwarding any passed in arguments to its constructor.
*
* `this->valid()` will return `true` for the newly constructed object.
*/
template <
class... Args,
@ -87,15 +78,14 @@ public:
/**
* \brief Move constructor.
* \details \ref resultset and \ref statement objects referencing `other` will remain valid.
* \details \ref statement objects referencing `other` remain usable for I/O operations.
*/
connection(connection&& other) = default;
/**
* \brief Move assignment.
* \details \ref resultset and \ref statement objects referencing `other` will remain valid.
* Objects referencing `*this` will no longer be valid. They can be re-used
* in I/O object generting operations like \ref query or \ref prepare_statement.
* \details \ref statement objects referencing `other` remain usable for I/O operations.
* Statements referencing `*this` will no longer be usable.
*/
connection& operator=(connection&& rhs) = default;
@ -104,14 +94,6 @@ public:
connection& operator=(const connection&) = delete;
#endif
/**
* \brief Returns `true` if the object is in a valid state.
* \details This function always returns `true` except for moved-from
* connections. Being `valid()` is a precondition for all network
* operations in this class.
*/
bool valid() const noexcept { return channel_ != nullptr; }
/// The executor type associated to this object.
using executor_type = typename Stream::executor_type;
@ -130,9 +112,6 @@ public:
/// The type of prepared statements that can be used with this connection type.
using statement_type = statement<Stream>;
/// The type of resultsets that can be used with this connection type.
using resultset_type = resultset<Stream>;
/**
* \brief Returns whether the connection uses SSL or not.
* \details This function always returns `false` if the underlying
@ -214,7 +193,7 @@ public:
/**
* \brief Performs the MySQL-level handshake.
* \details Does not connect the underlying stream.
* If the `Stream` template parameter fulfills the __SocketConnection__
* If the `Stream` template parameter fulfills the `SocketConnection`
* requirements, use \ref connection::connect instead of this function.
*\n
* If using a SSL-capable stream, the SSL handshake will be performed by this function.
@ -257,23 +236,18 @@ public:
/**
* \brief Executes a SQL text query.
* \details Starts a multi-function operation. This function will write the query request to the
* server and read the initial server response, but won't read the generated rows, if any. After
* this operation completes, `result` will have \ref resultset::meta populated, and may become
* \ref resultset::complete, if the operation did not generate any rows (e.g. it was an
* `UPDATE`). `result` will reference `*this`, and will be usable for server interaction as long
* as I/O object references to `*this` are valid.
* \details
* Sends `query_string` to the server for execution and reads the response into `result`.
* `query_string` should be encoded using the connection's character set.
*\n
* If the operation generated any rows, these __must__ be read (by using any of the
* `resultset::read_xxx` functions) before engaging in any further operation involving server
* communication. Otherwise, the results are undefined.
* After this operation completes successfully, `result.has_value() == true`.
*\n
* This operation involves both reads and writes on the underlying stream.
*/
void query(boost::string_view query_string, resultset<Stream>& result, error_code&, error_info&);
void query(boost::string_view query_string, resultset& result, error_code&, error_info&);
/// \copydoc query
void query(boost::string_view query_string, resultset<Stream>& result);
void query(boost::string_view query_string, resultset& result);
/**
* \copydoc query
@ -281,7 +255,7 @@ public:
* If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the string
* pointed to by `query_string` __must be kept alive by the caller until the operation is
* initiated__.
*
*\n
* The handler signature for this operation is `void(boost::mysql::error_code)`.
*/
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
@ -289,7 +263,7 @@ public:
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_query(
boost::string_view query_string,
resultset<Stream>& result,
resultset& result,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
@ -307,7 +281,64 @@ public:
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_query(
boost::string_view query_string,
resultset<Stream>& result,
resultset& result,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \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`).
*\n
* If the operation generated any rows, these <b>must</b> be read (by using \ref read_one_row or
* \ref read_some_rows) before engaging in any further operation involving network reads.
* Otherwise, the results are undefined.
*\n
* This operation involves both reads and writes on the underlying stream.
*\n
* `query_string` should be encoded using the connection's character set.
*/
void start_query(boost::string_view query_string, execution_state& st, error_code&, error_info&);
/// \copydoc start_query
void start_query(boost::string_view query_string, execution_state& st);
/**
* \copydoc start_query
* \details
* If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the string
* pointed to by `query_string` <b>must be kept alive by the caller until the operation is
* initiated</b>.
*
* 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_start_query(
boost::string_view query_string,
execution_state& st,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_start_query(
query_string,
st,
shared_info(),
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_start_query
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_start_query(
boost::string_view query_string,
execution_state& st,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
@ -315,10 +346,11 @@ public:
/**
* \brief Prepares a statement server-side.
* \details
* After this operation completes, `result` will reference `*this`. It will be usable for server
* interaction as long as I/O object references to `*this` are valid.
* After this operation completes, `result` will reference `*this`.
*\n
* This operation involves both reads and writes on the underlying stream.
*\n
* `stmt` should be encoded using the connection's character set.
*/
void prepare_statement(boost::string_view stmt, statement<Stream>& result, error_code&, error_info&);
@ -329,8 +361,8 @@ public:
* \copydoc prepare_statement
* \details
* If `CompletionToken` is a deferred completion token (e.g. `use_awaitable`), the string
* pointed to by `stmt` __must be kept alive by the caller until the operation is
* initiated__.
* pointed to by `stmt` <b>must be kept alive by the caller until the operation is
* initiated</b>.
*\n
* The handler signature for this operation is `void(boost::mysql::error_code)`
*/
@ -363,14 +395,118 @@ public:
);
/**
* \brief Closes the connection with the server.
* \brief Reads a single row.
* \details
* If a row was read successfully, returns a non-empty \ref row_view.
* If there were no more rows to read, returns an empty `row_view`.
*\n
* The returned view points into memory owned by `*this`. It will be valid until the
* underlying stream performs any other read operation or is destroyed.
*\n
* `st` must have previously been populated by a function starting the multifunction
* operation, like \ref start_query or \ref statement::start_execution. Otherwise, the results
* are undefined.
*/
row_view read_one_row(execution_state& st, error_code& err, error_info& info);
/// \copydoc read_one_row
row_view read_one_row(execution_state& st);
/**
* \copydoc read_one_row
*
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::row_view)`.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, row_view))
async_read_one_row(
execution_state& st,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_read_one_row(
st,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_read_one_row
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, row_view))
async_read_one_row(
execution_state& st,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \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`.
* \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 \ref
* connection::connect, using \ref buffer_params::initial_read_buffer_size. The buffer may be
* grown bigger by other read operations, if required.
* \n
* The returned view points into memory owned by `*this`. It will be valid until the
* underlying stream performs any other read operation or is destroyed.
*/
rows_view read_some_rows(execution_state& st, error_code& err, error_info& info);
/// \copydoc read_some_rows
rows_view read_some_rows(execution_state& st);
/**
* \copydoc read_some_rows
* \details
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::rows_view)`.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some_rows(
execution_state& st,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_read_some_rows(
st,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_read_some_rows
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some_rows(
execution_state& st,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Closes the connection to the server.
* \details
* This function is only available if `Stream` satisfies the `SocketStream` concept.
*\n
* Sends a quit request, performs the TLS shutdown (if required)
* and closes the underlying stream. Prefer this function to \ref connection::quit.
*\n
* After calling this function, any \ref statement and \ref resultset referencing `*this` will
* After calling this function, any \ref statement referencing `*this` will
* no longer be usable for server interaction.
*\n
*/
@ -407,7 +543,10 @@ public:
* this function will also perform the SSL shutdown. You should
* close the underlying physical connection after calling this function.
*\n
* If the `Stream` template parameter fulfills the __SocketConnection__
* After calling this function, any \ref statement referencing `*this` will
* no longer be usable for server interaction.
*\n
* If the `Stream` template parameter fulfills the `SocketConnection`
* requirements, use \ref connection::close instead of this function,
* as it also takes care of closing the underlying stream.
*/

View File

@ -8,9 +8,10 @@
#ifndef BOOST_MYSQL_DETAIL_AUXILIAR_FIELD_TYPE_TRAITS_HPP
#define BOOST_MYSQL_DETAIL_AUXILIAR_FIELD_TYPE_TRAITS_HPP
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/detail/auxiliar/void_t.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>

View File

@ -8,9 +8,10 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_CONNECTION_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_CONNECTION_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -18,7 +19,9 @@ namespace detail {
template <class SocketStream>
void close_connection(channel<SocketStream>& chan, error_code& code, error_info& info);
template <class SocketStream, class CompletionToken>
template <
class SocketStream,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_close_connection(channel<SocketStream>& chan, CompletionToken&& token, error_info& info);

View File

@ -8,10 +8,11 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -24,7 +25,9 @@ void close_statement(
error_info& info
);
template <class Stream, class CompletionToken>
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_close_statement(
channel<Stream>& chan,

View File

@ -8,10 +8,11 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CONNECT_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CONNECT_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -25,7 +26,9 @@ void connect(
error_info& info
);
template <class Stream, class CompletionToken>
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_connect(
channel<Stream>& chan,

View File

@ -1,95 +0,0 @@
//
// 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)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_GENERIC_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_GENERIC_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/prepared_statement_messages.hpp>
#include <boost/mysql/detail/protocol/query_messages.hpp>
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <type_traits>
#include <utility>
#ifdef BOOST_MYSQL_HAS_CONCEPTS
#include <concepts>
#endif
namespace boost {
namespace mysql {
namespace detail {
#ifdef BOOST_MYSQL_HAS_CONCEPTS
// clang-format off
template <class T>
struct is_com_stmt_execute_packet : std::false_type {};
template <class FieldViewFwdIterator>
struct is_com_stmt_execute_packet<com_stmt_execute_packet<FieldViewFwdIterator>> : std::true_type {};
template <class T>
concept execute_request = std::is_same_v<T, com_query_packet> || is_com_stmt_execute_packet<T>::value;
template <class T>
concept execute_request_maker = requires(const T& t)
{
typename T::storage_type; // Used by prepared statements (tuple version) to store field_view's
requires std::default_initializable<typename T::storage_type>;
requires std::copy_constructible<T>;
{ t.make_storage() } -> std::same_as<typename T::storage_type>;
{ t.make_request(std::declval<const typename T::storage_type&>()) } -> execute_request;
};
// clang-format on
#define BOOST_MYSQL_EXECUTE_REQUEST ::boost::mysql::detail::execute_request
#define BOOST_MYSQL_EXECUTE_REQUEST_MAKER ::boost::mysql::detail::execute_request_maker
#else // BOOST_MYSQL_HAS_CONCEPTS
#define BOOST_MYSQL_EXECUTE_REQUEST class
#define BOOST_MYSQL_EXECUTE_REQUEST_MAKER 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_EXECUTE_REQUEST Request>
void execute_generic(
resultset_encoding encoding,
channel<Stream>& channel,
const Request& request,
resultset_base& output,
error_code& err,
error_info& info
);
// 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_EXECUTE_REQUEST_MAKER RequestMaker, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute_generic(
resultset_encoding encoding,
channel<Stream>& chan,
RequestMaker&& reqmaker, // this should always by an rvalue
resultset_base& output,
error_info& info,
CompletionToken&& token
);
} // namespace detail
} // namespace mysql
} // namespace boost
#include <boost/mysql/detail/network_algorithms/impl/execute_generic.hpp>
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP_ */

View File

@ -8,64 +8,39 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/execute_options.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/asio/async_result.hpp>
namespace boost {
namespace mysql {
namespace detail {
template <class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void execute_statement(
channel<Stream>& channel,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& opts,
resultset_base& output,
error_code& err,
error_info& info
);
template <
class Stream,
BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator,
class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute_statement(
channel<Stream>& chan,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& opts,
resultset_base& output,
error_info& info,
CompletionToken&& token
);
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
void execute_statement(
channel<Stream>& channel,
const statement_base& stmt,
const FieldLikeTuple& params,
const execute_options& opts,
resultset_base& output,
resultset& output,
error_code& err,
error_info& info
);
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class CompletionToken>
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_base& stmt,
FieldLikeTuple&& params,
const execute_options& opts,
resultset_base& output,
resultset& output,
error_info& info,
CompletionToken&& token
);
@ -76,4 +51,4 @@ async_execute_statement(
#include <boost/mysql/detail/network_algorithms/impl/execute_statement.hpp>
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP_ */
#endif

View File

@ -8,10 +8,11 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HANDSHAKE_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_HANDSHAKE_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -24,7 +25,9 @@ void handshake(
error_info& info
);
template <class Stream, class CompletionToken>
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_handshake(
channel<Stream>& channel,

View File

@ -81,7 +81,9 @@ void boost::mysql::detail::close_connection(
}
}
template <class SocketStream, class CompletionToken>
template <
class SocketStream,
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_close_connection(
channel<SocketStream>& chan,

View File

@ -90,7 +90,9 @@ void boost::mysql::detail::
stmt.reset();
}
template <class Stream, class 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::async_close_statement(
channel<Stream>& chan,

View File

@ -92,7 +92,9 @@ void boost::mysql::detail::connect(
}
}
template <class Stream, class 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::async_connect(
channel<Stream>& chan,

View File

@ -10,162 +10,78 @@
#pragma once
#include <boost/mysql/detail/auxiliar/stringize.hpp>
#include <boost/mysql/detail/network_algorithms/execute_generic.hpp>
#include <boost/mysql/detail/network_algorithms/execute_statement.hpp>
#include <boost/mysql/detail/protocol/prepared_statement_messages.hpp>
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/resultset.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/post.hpp>
#include <boost/mp11/integer_sequence.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 <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)
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
struct execute_statement_op : boost::asio::coroutine
{
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_request_maker_it
{
std::uint32_t stmt_id_;
FieldViewFwdIterator first_;
FieldViewFwdIterator last_;
public:
struct storage_type
{
};
stmt_execute_request_maker_it(
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)
{
}
storage_type make_storage() const noexcept { return storage_type(); }
com_stmt_execute_packet<FieldViewFwdIterator> make_request(storage_type) const
noexcept(std::is_nothrow_copy_constructible<FieldViewFwdIterator>::value)
{
return make_stmt_execute_packet(stmt_id_, first_, last_);
}
};
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
class stmt_execute_request_maker_tuple
{
std::uint32_t stmt_id_;
channel<Stream>& chan_;
error_info& output_info_;
const statement_base& stmt_;
FieldLikeTuple params_;
public:
using storage_type = std::array<field_view, std::tuple_size<FieldLikeTuple>::value>;
resultset& output_;
// We need a deduced context to enable perfect forwarding
template <BOOST_MYSQL_FIELD_LIKE_TUPLE DeducedTuple>
stmt_execute_request_maker_tuple(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))
execute_statement_op(
channel<Stream>& chan,
error_info& output_info,
const statement_base& stmt,
DeducedTuple&& params,
resultset& output
) noexcept
: chan_(chan),
output_info_(output_info),
stmt_(stmt),
params_(std::forward<DeducedTuple>(params)),
output_(output)
{
}
storage_type make_storage() const noexcept { return tuple_to_array(params_); }
com_stmt_execute_packet<const field_view*> make_request(const storage_type& st) const
{
return make_stmt_execute_packet(stmt_id_, st.data(), st.data() + st.size());
}
};
inline error_code check_num_params(
const statement_base& stmt,
std::size_t param_count,
error_info& info
)
{
if (param_count != stmt.num_params())
{
info.set_message(detail::stringize(
"execute statement: expected ",
stmt.num_params(),
" params, but got ",
param_count
));
return make_error_code(errc::wrong_num_params);
}
else
{
return error_code();
}
}
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
error_code check_num_params(
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
error_info& info
)
{
return check_num_params(stmt, std::distance(params_first, params_last), info);
}
struct fast_fail_op : boost::asio::coroutine
{
error_code err_;
fast_fail_op(error_code err) noexcept : err_(err) {}
template <class Self>
void operator()(Self& 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 boost::asio::post(std::move(self));
self.complete(err_);
BOOST_ASIO_CORO_YIELD
async_start_statement_execution(
chan_,
stmt_,
std::move(params_),
output_.state(),
output_info_,
std::move(self)
);
BOOST_ASIO_CORO_YIELD async_read_all_rows(
chan_,
output_.state(),
output_.mutable_rows(),
output_info_,
std::move(self)
);
self.complete(error_code());
}
}
};
@ -174,124 +90,49 @@ struct fast_fail_op : boost::asio::coroutine
} // namespace mysql
} // namespace boost
template <class Stream, BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void boost::mysql::detail::execute_statement(
channel<Stream>& chan,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options&,
resultset_base& output,
error_code& err,
error_info& info
)
{
err = check_num_params(stmt, params_first, params_last, info);
if (!err)
{
execute_generic(
resultset_encoding::binary,
chan,
make_stmt_execute_packet(stmt.id(), params_first, params_last),
output,
err,
info
);
}
}
template <
class Stream,
BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator,
class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::detail::async_execute_statement(
channel<Stream>& chan,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options&,
resultset_base& output,
error_info& info,
CompletionToken&& token
)
{
error_code err = check_num_params(stmt, params_first, params_last, info);
if (err)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
fast_fail_op(err),
token,
chan
);
}
return async_execute_generic(
resultset_encoding::binary,
chan,
stmt_execute_request_maker_it<FieldViewFwdIterator>(stmt.id(), params_first, params_last),
output,
info,
std::forward<CompletionToken>(token)
);
}
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
void boost::mysql::detail::execute_statement(
channel<Stream>& channel,
const statement_base& stmt,
const FieldLikeTuple& params,
const execute_options& opts,
resultset_base& output,
resultset& output,
error_code& err,
error_info& info
)
{
auto params_array = tuple_to_array(params);
execute_statement(
channel,
stmt,
params_array.begin(),
params_array.end(),
opts,
output,
err,
info
);
start_statement_execution(channel, stmt, params, output.state(), err, info);
if (err)
return;
read_all_rows(channel, output.state(), output.mutable_rows(), err, info);
}
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class CompletionToken>
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_base& stmt,
FieldLikeTuple&& params,
const execute_options&,
resultset_base& output,
resultset& output,
error_info& info,
CompletionToken&& token
)
{
using decayed_tuple = typename std::decay<FieldLikeTuple>::type;
error_code err = check_num_params(stmt, std::tuple_size<decayed_tuple>::value, info);
if (err)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
fast_fail_op(err),
token,
chan
);
}
return async_execute_generic(
resultset_encoding::binary,
chan,
stmt_execute_request_maker_tuple<decayed_tuple>(
stmt.id(),
std::forward<FieldLikeTuple>(params)
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
execute_statement_op<Stream, decayed_tuple>(
chan,
info,
stmt,
std::forward<FieldLikeTuple>(params),
output
),
output,
info,
std::forward<CompletionToken>(token)
token,
chan
);
}
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_STATEMENT_HPP_ */
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */

View File

@ -452,7 +452,9 @@ void boost::mysql::detail::handshake(
channel.set_current_capabilities(processor.negotiated_capabilities());
}
template <class Stream, class 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::async_handshake(
channel<Stream>& chan,

View File

@ -10,12 +10,13 @@
#pragma once
#include <boost/mysql/error.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/auxiliar/bytestring.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/detail/network_algorithms/prepare_statement.hpp>
#include <boost/mysql/detail/protocol/capabilities.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/asio/buffer.hpp>
@ -217,7 +218,9 @@ void boost::mysql::detail::prepare_statement(
}
}
template <class Stream, class 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::async_prepare_statement(
channel<Stream>& chan,

View File

@ -0,0 +1,107 @@
//
// 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)
//
#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_;
error_info& output_info_;
boost::string_view query_;
resultset& output_;
query_op(
channel<Stream>& chan,
error_info& output_info,
boost::string_view q,
resultset& output
) noexcept
: chan_(chan), output_info_(output_info), 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_, output_.state(), output_info_, std::move(self));
BOOST_ASIO_CORO_YIELD async_read_all_rows(
chan_,
output_.state(),
output_.mutable_rows(),
output_info_,
std::move(self)
);
self.complete(error_code());
}
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
template <class Stream>
void boost::mysql::detail::query(
channel<Stream>& channel,
boost::string_view query,
resultset& output,
error_code& err,
error_info& info
)
{
start_query(channel, query, output.state(), err, info);
if (err)
return;
read_all_rows(channel, output.state(), output.mutable_rows(), err, info);
}
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,
boost::string_view query,
resultset& output,
error_info& info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(boost::mysql::error_code)>(
query_op<Stream>(chan, info, query, output),
token,
chan
);
}
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP_ */

View File

@ -10,9 +10,10 @@
#pragma once
#include <boost/mysql/error.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/detail/network_algorithms/quit_connection.hpp>
#include <boost/mysql/error.hpp>
#include <boost/asio/coroutine.hpp>
@ -85,7 +86,9 @@ void boost::mysql::detail::quit_connection(channel<Stream>& chan, error_code& er
}
}
template <class Stream, class 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::async_quit_connection(
channel<Stream>& chan,

View File

@ -10,11 +10,12 @@
#pragma once
#include <boost/mysql/error.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/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/coroutine.hpp>
@ -29,8 +30,8 @@ namespace detail {
template <class Stream>
inline void process_all_rows(
channel<Stream>& channel,
resultset_base& result,
rows_view& output,
execution_state& st,
rows& output,
error_code& err,
error_info& info
)
@ -40,7 +41,7 @@ inline void process_all_rows(
while (channel.has_read_messages())
{
// Get the row message
auto message = channel.next_read_message(result.sequence_number(), err);
auto message = channel.next_read_message(st.sequence_number(), err);
if (err)
return;
@ -49,7 +50,7 @@ inline void process_all_rows(
message,
channel.current_capabilities(),
channel.buffer_first(),
result,
st,
channel.shared_fields(),
err,
info
@ -58,14 +59,14 @@ inline void process_all_rows(
return;
// If we received an EOF, we're done
if (result.complete())
if (st.complete())
{
offsets_to_string_views(channel.shared_fields(), channel.buffer_first());
output = rows_view(
channel.shared_fields().data(),
channel.shared_fields().size(),
result.meta().size()
st.meta().size()
);
offsets_to_string_views(channel.shared_fields(), channel.buffer_first());
break;
}
}
@ -76,85 +77,21 @@ struct read_all_rows_op : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
execution_state& st_;
rows& output_;
read_all_rows_op(
channel<Stream>& chan,
error_info& output_info,
resultset_base& result
execution_state& st,
rows& output
) noexcept
: chan_(chan), output_info_(output_info), resultset_(result)
: chan_(chan), output_info_(output_info), st_(st), output_(output)
{
}
template <class Self>
void operator()(Self& self, error_code err = {})
{
// Error checking
if (err)
{
self.complete(err, rows_view());
return;
}
// Normal path
rows_view output;
BOOST_ASIO_CORO_REENTER(*this)
{
output_info_.clear();
// If the resultset_base is already complete, we don't need to read anything
if (resultset_.complete())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
self.complete(error_code(), rows_view());
BOOST_ASIO_CORO_YIELD break;
}
// Clear anything from previous runs
chan_.shared_fields().clear();
// Read at least one message
while (!resultset_.complete())
{
// Actually read
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self), true);
// Process messages
process_all_rows(chan_, resultset_, output, err, output_info_);
if (err)
{
self.complete(err, rows_view());
BOOST_ASIO_CORO_YIELD break;
}
}
// Done
self.complete(error_code(), output);
}
}
};
template <class Stream>
struct read_all_rows_op_rows : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
rows& output_;
read_all_rows_op_rows(
channel<Stream>& chan,
error_info& output_info,
resultset_base& result,
rows& output
) noexcept
: chan_(chan), output_info_(output_info), resultset_(result), output_(output)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, rows_view rv = {})
{
// Error checking
if (err)
@ -166,13 +103,36 @@ struct read_all_rows_op_rows : boost::asio::coroutine
// Normal path
BOOST_ASIO_CORO_REENTER(*this)
{
// error_info already cleared by child ops
output_info_.clear();
output_.clear();
BOOST_ASIO_CORO_YIELD
async_read_all_rows(chan_, resultset_, output_info_, std::move(self));
// If the resultset_base 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;
}
output_ = rv;
// 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, output_info_);
if (err)
{
self.complete(err);
BOOST_ASIO_CORO_YIELD break;
}
}
// Done
self.complete(error_code());
}
}
@ -182,83 +142,56 @@ struct read_all_rows_op_rows : boost::asio::coroutine
} // namespace mysql
} // namespace boost
template <class Stream>
boost::mysql::rows_view boost::mysql::detail::read_all_rows(
channel<Stream>& channel,
resultset_base& result,
error_code& err,
error_info& info
)
{
// If the resultset_base is already complete, we don't need to read anything
if (result.complete())
{
return rows_view();
}
// Clear anything from previous runs
channel.shared_fields().clear();
rows_view output;
while (!result.complete())
{
// Read from the stream until there is at least one message
channel.read_some(err, true);
if (err)
return rows_view();
// Process read messages
process_all_rows(channel, result, output, err, info);
if (err)
return rows_view();
}
return output;
}
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::rows_view)
)
boost::mysql::detail::async_read_all_rows(
channel<Stream>& channel,
resultset_base& result,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
read_all_rows_op<Stream>(channel, output_info, result),
token,
channel
);
}
template <class Stream>
void boost::mysql::detail::read_all_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
rows& output,
error_code& err,
error_info& info
)
{
// TODO: this can be optimized
output = read_all_rows(channel, result, err, info);
info.clear();
output.clear();
// If the resultset_base 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, info);
if (err)
return;
}
}
template <class Stream, class 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::async_read_all_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
rows& output,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
read_all_rows_op_rows<Stream>(channel, output_info, result, output),
read_all_rows_op<Stream>(channel, output_info, st, output),
token,
channel
);

View File

@ -10,10 +10,11 @@
#pragma once
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/detail/network_algorithms/read_one_row.hpp>
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/post.hpp>
@ -28,7 +29,7 @@ template <class Stream>
inline row_view process_one_row(
channel<Stream>& channel,
boost::asio::const_buffer read_message,
resultset_base& result,
execution_state& st,
error_code& err,
error_info& info
)
@ -41,13 +42,13 @@ inline row_view process_one_row(
read_message,
channel.current_capabilities(),
channel.buffer_first(),
result,
st,
channel.shared_fields(),
err,
info
);
if (!err && !result.complete())
if (!err && !st.complete())
{
offsets_to_string_views(channel.shared_fields(), channel.buffer_first());
return row_view(channel.shared_fields().data(), channel.shared_fields().size());
@ -63,10 +64,10 @@ struct read_one_row_op : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
execution_state& st_;
read_one_row_op(channel<Stream>& chan, error_info& output_info, resultset_base& result) noexcept
: chan_(chan), output_info_(output_info), resultset_(result)
read_one_row_op(channel<Stream>& chan, error_info& output_info, execution_state& st) noexcept
: chan_(chan), output_info_(output_info), st_(st)
{
}
@ -87,7 +88,7 @@ struct read_one_row_op : boost::asio::coroutine
output_info_.clear();
// If the resultset is already complete, we don't need to read anything
if (resultset_.complete())
if (st_.complete())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
self.complete(error_code(), row_view());
@ -95,61 +96,15 @@ struct read_one_row_op : boost::asio::coroutine
}
// Read the message
BOOST_ASIO_CORO_YIELD chan_.async_read_one(
resultset_.sequence_number(),
std::move(self)
);
BOOST_ASIO_CORO_YIELD chan_.async_read_one(st_.sequence_number(), std::move(self));
// Process it
result = process_one_row(chan_, read_message, resultset_, err, output_info_);
result = process_one_row(chan_, read_message, st_, err, output_info_);
self.complete(err, result);
}
}
};
template <class Stream>
struct read_one_row_op_row : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
row& output_;
read_one_row_op_row(
channel<Stream>& chan,
error_info& output_info,
resultset_base& result,
row& output
) noexcept
: chan_(chan), output_info_(output_info), resultset_(result), output_(output)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, row_view rv = {})
{
// Error checking
if (err)
{
self.complete(err, false);
return;
}
// Normal path
BOOST_ASIO_CORO_REENTER(*this)
{
// error_info already cleared by child ops
output_.clear();
BOOST_ASIO_CORO_YIELD
async_read_one_row(chan_, resultset_, output_info_, std::move(self));
output_ = rv;
self.complete(error_code(), !rv.empty());
}
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
@ -157,7 +112,7 @@ struct read_one_row_op_row : boost::asio::coroutine
template <class Stream>
boost::mysql::row_view boost::mysql::detail::read_one_row(
channel<Stream>& channel,
resultset_base& result,
execution_state& result,
error_code& err,
error_info& info
)
@ -176,51 +131,23 @@ boost::mysql::row_view boost::mysql::detail::read_one_row(
return process_one_row(channel, read_message, result, err, info);
}
template <class Stream>
bool boost::mysql::detail::read_one_row(
channel<Stream>& channel,
resultset_base& result,
row& output,
error_code& err,
error_info& info
)
{
// TODO: this can be optimized
output = read_one_row(channel, result, err, info);
return !output.empty();
}
template <class Stream, class CompletionToken>
template <
class Stream,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::row_view)
)
boost::mysql::detail::async_read_one_row(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, row_view)>(
read_one_row_op<Stream>(channel, output_info, result),
token,
channel
);
}
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, bool))
boost::mysql::detail::async_read_one_row(
channel<Stream>& channel,
resultset_base& result,
row& output,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, bool)>(
read_one_row_op_row<Stream>(channel, output_info, result, output),
read_one_row_op<Stream>(channel, output_info, st),
token,
channel
);

View File

@ -10,11 +10,12 @@
#pragma once
#include <boost/mysql/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/detail/network_algorithms/read_some_rows.hpp>
#include <boost/mysql/detail/protocol/deserialize_row.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/row.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/post.hpp>
@ -28,7 +29,7 @@ namespace detail {
template <class Stream>
inline rows_view process_some_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_code& err,
error_info& info
)
@ -40,7 +41,7 @@ inline rows_view process_some_rows(
while (channel.has_read_messages())
{
// Get the row message
auto message = channel.next_read_message(result.sequence_number(), err);
auto message = channel.next_read_message(st.sequence_number(), err);
if (err)
return rows_view();
@ -49,7 +50,7 @@ inline rows_view process_some_rows(
message,
channel.current_capabilities(),
channel.buffer_first(),
result,
st,
channel.shared_fields(),
err,
info
@ -61,7 +62,7 @@ inline rows_view process_some_rows(
// will point into the channel's internal buffer
// If we received an EOF, we're done
if (result.complete())
if (st.complete())
break;
++num_rows;
}
@ -69,8 +70,8 @@ inline rows_view process_some_rows(
return rows_view(
channel.shared_fields().data(),
num_rows * result.fields().size(),
result.fields().size()
num_rows * st.fields().size(),
st.fields().size()
);
}
@ -79,14 +80,10 @@ struct read_some_rows_op : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
execution_state& st_;
read_some_rows_op(
channel<Stream>& chan,
error_info& output_info,
resultset_base& result
) noexcept
: chan_(chan), output_info_(output_info), resultset_(result)
read_some_rows_op(channel<Stream>& chan, error_info& output_info, execution_state& st) noexcept
: chan_(chan), output_info_(output_info), st_(st)
{
}
@ -107,7 +104,7 @@ struct read_some_rows_op : boost::asio::coroutine
output_info_.clear();
// If the resultset is already complete, we don't need to read anything
if (resultset_.complete())
if (st_.complete())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
self.complete(error_code(), rows_view());
@ -118,56 +115,13 @@ 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_, resultset_, err, output_info_);
output = process_some_rows(chan_, st_, err, output_info_);
self.complete(err, output);
}
}
};
template <class Stream>
struct read_some_rows_op_rows : boost::asio::coroutine
{
channel<Stream>& chan_;
error_info& output_info_;
resultset_base& resultset_;
rows& output_;
read_some_rows_op_rows(
channel<Stream>& chan,
error_info& output_info,
resultset_base& result,
rows& output
) noexcept
: chan_(chan), output_info_(output_info), resultset_(result), output_(output)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, rows_view rv = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Normal path
BOOST_ASIO_CORO_REENTER(*this)
{
// output_info already cleared by child ops
output_.clear();
BOOST_ASIO_CORO_YIELD
async_read_some_rows(chan_, resultset_, output_info_, std::move(self));
output_ = rv;
self.complete(err);
}
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
@ -175,13 +129,13 @@ struct read_some_rows_op_rows : boost::asio::coroutine
template <class Stream>
boost::mysql::rows_view boost::mysql::detail::read_some_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_code& err,
error_info& info
)
{
// If the resultset is already complete, we don't need to read anything
if (result.complete())
if (st.complete())
{
return rows_view();
}
@ -192,53 +146,26 @@ boost::mysql::rows_view boost::mysql::detail::read_some_rows(
return rows_view();
// Process read messages
return process_some_rows(channel, result, err, info);
return process_some_rows(channel, st, err, info);
}
template <class Stream>
void boost::mysql::detail::read_some_rows(
channel<Stream>& channel,
resultset_base& result,
rows& output,
error_code& err,
error_info& info
)
{
// TODO: this can be optimized
output = read_some_rows(channel, result, err, info);
}
template <class Stream, class CompletionToken>
template <
class Stream,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::rows_view)
)
boost::mysql::detail::async_read_some_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
read_some_rows_op<Stream>(channel, output_info, result),
token,
channel
);
}
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::detail::async_read_some_rows(
channel<Stream>& channel,
resultset_base& result,
rows& output,
error_info& output_info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
read_some_rows_op_rows<Stream>(channel, output_info, result, output),
read_some_rows_op<Stream>(channel, output_info, st),
token,
channel
);

View File

@ -5,18 +5,19 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_GENERIC_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_GENERIC_HPP
#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/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/detail/auxiliar/bytestring.hpp>
#include <boost/mysql/detail/network_algorithms/execute_generic.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/resultset_encoding.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/asio/buffer.hpp>
@ -28,10 +29,10 @@ namespace boost {
namespace mysql {
namespace detail {
class execute_processor
class start_execution_processor
{
resultset_encoding encoding_;
resultset_base& output_;
execution_state& st_;
error_info& output_info_;
bytestring& write_buffer_;
capabilities caps_;
@ -39,88 +40,44 @@ class execute_processor
std::size_t remaining_fields_{};
public:
execute_processor(
start_execution_processor(
resultset_encoding encoding,
resultset_base& output,
execution_state& st,
error_info& output_info,
bytestring& write_buffer,
capabilities caps
) noexcept
: encoding_(encoding),
output_(output),
st_(st),
output_info_(output_info),
write_buffer_(write_buffer),
caps_(caps){};
void clear_output_info() noexcept { output_info_.clear(); }
template <BOOST_MYSQL_EXECUTE_REQUEST Request, class Stream>
void process_request(const Request& request, channel<Stream>& chan)
template <BOOST_MYSQL_SERIALIZE_FN SerializeFn>
void process_request(SerializeFn&& request)
{
output_.reset(&chan, encoding_);
serialize_message(request, caps_, write_buffer_);
st_.reset(encoding_);
std::forward<SerializeFn>(request)(caps_, write_buffer_);
// serialize_message(request, caps_, write_buffer_);
}
template <BOOST_MYSQL_EXECUTE_REQUEST_MAKER RequestMaker, class Stream>
void process_request_maker(const RequestMaker& reqmaker, channel<Stream>& chan)
void process_response(boost::asio::const_buffer msg, error_code& err)
{
auto st = reqmaker.make_storage();
process_request(reqmaker.make_request(st), chan);
}
void process_response(boost::asio::const_buffer response, error_code& err)
{
// 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(response, caps_);
std::uint8_t msg_type = 0;
err = make_error_code(deserialize(ctx, msg_type));
if (err)
return;
if (msg_type == ok_packet_header)
auto response = deserialize_execute_response(msg, caps_, output_info_);
switch (response.type)
{
ok_packet ok_pack;
err = deserialize_message(ctx, ok_pack);
if (err)
return;
output_.complete(ok_pack);
case execute_response::type_t::error: err = response.data.err; break;
case execute_response::type_t::ok_packet:
st_.complete(response.data.ok_pack);
num_fields_ = 0;
}
else if (msg_type == error_packet_header)
{
err = process_error_packet(ctx, output_info_);
}
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;
// For platforms where size_t is shorter than uint64_t,
// perform range check
if (num_fields.value > std::numeric_limits<std::size_t>::max())
{
err = make_error_code(errc::protocol_value_error);
return;
}
// Ensure we have fields, as field_count is indicative of
// a resultset with fields
num_fields_ = static_cast<std::size_t>(num_fields.value);
if (num_fields_ == 0)
{
err = make_error_code(errc::protocol_value_error);
return;
}
break;
case execute_response::type_t::num_fields:
num_fields_ = response.data.num_fields;
remaining_fields_ = num_fields_;
output_.prepare_meta(num_fields_);
st_.prepare_meta(num_fields_);
break;
}
}
@ -133,29 +90,30 @@ public:
return err;
// Add it to our array
output_.add_meta(field_definition);
st_.add_meta(field_definition);
--remaining_fields_;
return error_code();
}
std::uint8_t& sequence_number() noexcept { return output_.sequence_number(); }
std::uint8_t& sequence_number() noexcept { return st_.sequence_number(); }
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_EXECUTE_REQUEST_MAKER RequestMaker>
struct execute_generic_op : boost::asio::coroutine
template <class Stream, BOOST_MYSQL_SERIALIZE_FN SerializeFn>
struct start_execution_generic_op : boost::asio::coroutine
{
channel<Stream>& chan_;
RequestMaker reqmaker_;
execute_processor processor_;
SerializeFn serialize_fn_;
start_execution_processor processor_;
execute_generic_op(
template <class DeducedSerializeFn>
start_execution_generic_op(
channel<Stream>& chan,
RequestMaker&& reqmaker,
const execute_processor& processor
DeducedSerializeFn&& fn,
const start_execution_processor& processor
)
: chan_(chan), reqmaker_(std::move(reqmaker)), processor_(processor)
: chan_(chan), serialize_fn_(std::forward<DeducedSerializeFn>(fn)), processor_(processor)
{
}
@ -175,7 +133,7 @@ struct execute_generic_op : boost::asio::coroutine
processor_.clear_output_info();
// Serialize the request
processor_.process_request_maker(reqmaker_, chan_);
processor_.process_request(std::move(serialize_fn_));
// Send it
BOOST_ASIO_CORO_YIELD chan_
@ -232,20 +190,72 @@ struct execute_generic_op : boost::asio::coroutine
} // namespace mysql
} // namespace boost
template <class Stream, BOOST_MYSQL_EXECUTE_REQUEST Request>
void boost::mysql::detail::execute_generic(
inline boost::mysql::detail::execute_response boost::mysql::detail::deserialize_execute_response(
boost::asio::const_buffer msg,
capabilities caps,
error_info& info
) 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;
error_code err = make_error_code(deserialize(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, info);
}
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(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 Request& request,
resultset_base& output,
const SerializeFn& fn,
execution_state& st,
error_code& err,
error_info& info
)
{
// Clear info
info.clear();
// Serialize the request
execute_processor
processor(encoding, output, info, channel.shared_buffer(), channel.current_capabilities());
processor.process_request(request, channel);
start_execution_processor
processor(encoding, st, info, channel.shared_buffer(), channel.current_capabilities());
processor.process_request(fn);
// Send it
channel.write(channel.shared_buffer(), processor.sequence_number(), err);
@ -286,24 +296,27 @@ void boost::mysql::detail::execute_generic(
}
}
template <class Stream, BOOST_MYSQL_EXECUTE_REQUEST_MAKER RequestMaker, class CompletionToken>
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_execute_generic(
boost::mysql::detail::async_start_execution_generic(
resultset_encoding encoding,
channel<Stream>& channel,
RequestMaker&& reqmaker,
resultset_base& output,
SerializeFn&& fn,
execution_state& st,
error_info& info,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
execute_generic_op<Stream, typename std::decay<RequestMaker>::type>(
start_execution_generic_op<Stream, typename std::decay<SerializeFn>::type>(
channel,
std::move(reqmaker),
execute_processor(
std::forward<SerializeFn>(fn),
start_execution_processor(
encoding,
output,
st,
info,
channel.shared_buffer(),
channel.current_capabilities()

View File

@ -5,15 +5,18 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_QUERY_HPP
#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/detail/network_algorithms/execute_generic.hpp>
#include <boost/mysql/detail/network_algorithms/execute_query.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>
#include <boost/utility/string_view_fwd.hpp>
@ -21,20 +24,16 @@ namespace boost {
namespace mysql {
namespace detail {
class query_request_maker
class query_serialize_fn
{
boost::string_view query_;
public:
struct storage_type
query_serialize_fn(boost::string_view query) noexcept : query_(query) {}
void operator()(capabilities caps, std::vector<std::uint8_t>& buffer) const
{
};
query_request_maker(boost::string_view query) noexcept : query_(query) {}
storage_type make_storage() const noexcept { return storage_type(); }
com_query_packet make_request(storage_type) const noexcept
{
return com_query_packet{string_eof(query_)};
com_query_packet request{string_eof(query_)};
serialize_message(request, caps, buffer);
}
};
@ -43,38 +42,40 @@ public:
} // namespace boost
template <class Stream>
void boost::mysql::detail::execute_query(
void boost::mysql::detail::start_query(
channel<Stream>& channel,
boost::string_view query,
resultset_base& output,
execution_state& output,
error_code& err,
error_info& info
)
{
execute_generic(
start_execution_generic(
resultset_encoding::text,
channel,
com_query_packet{string_eof(query)},
query_serialize_fn(query),
output,
err,
info
);
}
template <class Stream, class 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::async_execute_query(
boost::mysql::detail::async_start_query(
channel<Stream>& chan,
boost::string_view query,
resultset_base& output,
execution_state& output,
error_info& info,
CompletionToken&& token
)
{
return async_execute_generic(
return async_start_execution_generic(
resultset_encoding::text,
chan,
query_request_maker(query),
query_serialize_fn(query),
output,
info,
std::forward<CompletionToken>(token)

View File

@ -0,0 +1,285 @@
//
// 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)
//
#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/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/statement_base.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_base& stmt, std::size_t param_count)
{
if (param_count != stmt.num_params())
{
return make_error_code(errc::wrong_num_params);
}
else
{
return error_code();
}
}
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
error_code check_num_params(
const statement_base& 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_;
error_info& output_info_;
fast_fail_op(error_code err, error_info& info) noexcept : err_(err), output_info_(info) {}
template <class Self>
void operator()(Self& self)
{
BOOST_ASIO_CORO_REENTER(*this)
{
BOOST_ASIO_CORO_YIELD boost::asio::post(std::move(self));
output_info_.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_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
execution_state& output,
error_code& err,
error_info& info
)
{
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,
info
);
}
}
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_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
execution_state& output,
error_info& info,
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, info),
token,
chan
);
}
return async_start_execution_generic(
resultset_encoding::binary,
chan,
stmt_execute_it_serialize_fn<FieldViewFwdIterator>(stmt.id(), params_first, params_last),
output,
info,
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_base& stmt,
const FieldLikeTuple& params,
execution_state& output,
error_code& err,
error_info& info
)
{
auto params_array = tuple_to_array(params);
start_statement_execution(
channel,
stmt,
params_array.begin(),
params_array.end(),
output,
err,
info
);
}
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_base& stmt,
FieldLikeTuple&& params,
execution_state& output,
error_info& info,
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, info),
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,
info,
std::forward<CompletionToken>(token)
);
}
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_EXECUTE_STATEMENT_HPP_ */

View File

@ -8,10 +8,11 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/utility/string_view.hpp>
namespace boost {
@ -27,7 +28,9 @@ void prepare_statement(
error_info& info
);
template <class Stream, class CompletionToken>
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_prepare_statement(
channel<Stream>& chan,

View File

@ -0,0 +1,50 @@
//
// 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)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUERY_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUERY_HPP
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/utility/string_view.hpp>
namespace boost {
namespace mysql {
namespace detail {
template <class Stream>
void query(
channel<Stream>& channel,
boost::string_view query,
resultset& output,
error_code& err,
error_info& info
);
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,
boost::string_view query,
resultset& output,
error_info& info,
CompletionToken&& token
);
} // namespace detail
} // namespace mysql
} // namespace boost
#include <boost/mysql/detail/network_algorithms/impl/query.hpp>
#endif

View File

@ -8,9 +8,10 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUIT_CONNECTION_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUIT_CONNECTION_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -18,7 +19,9 @@ namespace detail {
template <class Stream>
void quit_connection(channel<Stream>& chan, error_code& code, error_info& info);
template <class Stream, class CompletionToken>
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_quit_connection(channel<Stream>& chan, CompletionToken&& token, error_info& info);

View File

@ -8,47 +8,33 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ALL_ROWS_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ALL_ROWS_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/rows.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
template <class Stream>
rows_view read_all_rows(
channel<Stream>& channel,
resultset_base& result,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_all_rows(
channel<Stream>& channel,
resultset_base& result,
error_info& output_info,
CompletionToken&& token
);
template <class Stream>
void read_all_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
rows& output,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
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(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
rows& output,
error_info& output_info,
CompletionToken&& token

View File

@ -8,11 +8,12 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ONE_ROW_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_ONE_ROW_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -20,35 +21,19 @@ namespace detail {
template <class Stream>
row_view read_one_row(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
template <
class Stream,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, row_view))
async_read_one_row(
channel<Stream>& channel,
resultset_base& result,
error_info& output_info,
CompletionToken&& token
);
template <class Stream>
bool read_one_row(
channel<Stream>& channel,
resultset_base& result,
row& output,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool))
async_read_one_row(
channel<Stream>& channel,
resultset_base& result,
row& output,
execution_state& st,
error_info& output_info,
CompletionToken&& token
);

View File

@ -8,12 +8,13 @@
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/rows.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
namespace boost {
namespace mysql {
namespace detail {
@ -21,44 +22,19 @@ namespace detail {
template <class Stream>
rows_view read_some_rows(
channel<Stream>& channel,
resultset_base& result,
execution_state& st,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
template <
class Stream,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some_rows(
channel<Stream>& channel,
resultset_base& result,
error_info& output_info,
CompletionToken&& token
);
template <class Stream>
void read_some_rows(
channel<Stream>& channel,
resultset_base& result,
rows& output,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some_rows(
channel<Stream>& channel,
resultset_base& result,
error_info& output_info,
CompletionToken&& token
);
template <class Stream, class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_read_some_rows(
channel<Stream>& channel,
resultset_base& result,
rows& output,
execution_state& result,
error_info& output_info,
CompletionToken&& token
);

View File

@ -0,0 +1,115 @@
//
// 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)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_EXECUTION_GENERIC_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_EXECUTION_GENERIC_HPP
#include <boost/mysql/error.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/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,
error_info& info
) 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,
error_info& info
);
// 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,
error_info& info,
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_ */

View File

@ -5,13 +5,15 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_QUERY_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_QUERY_HPP
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_QUERY_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_QUERY_HPP
#include <boost/mysql/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/utility/string_view.hpp>
namespace boost {
@ -19,20 +21,22 @@ namespace mysql {
namespace detail {
template <class Stream>
void execute_query(
void start_query(
channel<Stream>& channel,
boost::string_view query,
resultset_base& output,
execution_state& output,
error_code& err,
error_info& info
);
template <class Stream, class CompletionToken>
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_execute_query(
async_start_query(
channel<Stream>& chan,
boost::string_view query,
resultset_base& output,
execution_state& output,
error_info& info,
CompletionToken&& token
);
@ -41,6 +45,6 @@ async_execute_query(
} // namespace mysql
} // namespace boost
#include <boost/mysql/detail/network_algorithms/impl/execute_query.hpp>
#include <boost/mysql/detail/network_algorithms/impl/start_query.hpp>
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_QUERY_HPP_ */

View File

@ -0,0 +1,80 @@
//
// 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)
//
#ifndef BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_STATEMENT_EXECUTION_HPP
#define BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_START_STATEMENT_EXECUTION_HPP
#include <boost/mysql/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/statement_base.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_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void start_statement_execution(
channel<Stream>& channel,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
execution_state& output,
error_code& err,
error_info& info
);
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(error_code))
async_start_statement_execution(
channel<Stream>& chan,
const statement_base& stmt,
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
execution_state& output,
error_info& info,
CompletionToken&& token
);
template <class Stream, BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple>
void start_statement_execution(
channel<Stream>& channel,
const statement_base& stmt,
const FieldLikeTuple& params,
execution_state& output,
error_code& err,
error_info& info
);
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_start_statement_execution(
channel<Stream>& chan,
const statement_base& stmt,
FieldLikeTuple&& params,
execution_state& output,
error_info& info,
CompletionToken&& token
);
} // namespace detail
} // namespace mysql
} // namespace boost
#include <boost/mysql/detail/network_algorithms/impl/start_statement_execution.hpp>
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_EXECUTE_STATEMENT_HPP_ */

View File

@ -9,6 +9,7 @@
#define BOOST_MYSQL_DETAIL_PROTOCOL_COMMON_MESSAGES_HPP
#include <boost/mysql/collation.hpp>
#include <boost/mysql/detail/protocol/constants.hpp>
#include <boost/mysql/detail/protocol/serialization.hpp>
@ -87,8 +88,6 @@ struct err_packet
}
};
static_assert(is_struct_with_fields<err_packet>(), "Bad!");
// col def
struct column_definition_packet
{

View File

@ -8,12 +8,13 @@
#ifndef BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_ROW_HPP
#define BOOST_MYSQL_DETAIL_PROTOCOL_DESERIALIZE_ROW_HPP
#include <boost/mysql/error.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/detail/protocol/capabilities.hpp>
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
#include <boost/mysql/detail/protocol/serialization_context.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/asio/buffer.hpp>
@ -37,7 +38,7 @@ inline void deserialize_row(
boost::asio::const_buffer read_message,
capabilities current_capabilities,
const std::uint8_t* buffer_first, // to store strings as offsets and allow buffer reallocation
resultset_base& result, // should be valid() and !complete()
execution_state& st, // should be !complete()
std::vector<field_view>& output,
error_code& err,
error_info& info

View File

@ -136,14 +136,13 @@ void boost::mysql::detail::deserialize_row(
boost::asio::const_buffer read_message,
capabilities current_capabilities,
const std::uint8_t* buffer_first, // to store strings as offsets and allow buffer reallocation
resultset_base& result,
execution_state& st,
std::vector<field_view>& output,
error_code& err,
error_info& info
)
{
assert(result.valid());
assert(!result.complete());
assert(!st.complete());
// Message type: row, error or eof?
std::uint8_t msg_type = 0;
@ -158,7 +157,7 @@ void boost::mysql::detail::deserialize_row(
err = deserialize_message(ctx, ok_pack);
if (err)
return;
result.complete(ok_pack);
st.complete(ok_pack);
}
else if (msg_type == error_packet_header)
{
@ -169,7 +168,7 @@ 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(result.encoding(), ctx, result.fields(), buffer_first, output, err);
deserialize_row(st.encoding(), ctx, st.fields(), buffer_first, output, err);
}
}

View File

@ -26,7 +26,7 @@ inline protocol_field_type get_protocol_field_type(const field_view& input) noex
case field_kind::null: return protocol_field_type::null;
case field_kind::int64: return protocol_field_type::longlong;
case field_kind::uint64: return protocol_field_type::longlong;
case field_kind::string: return protocol_field_type::varchar;
case field_kind::string: return protocol_field_type::string;
case field_kind::blob: return protocol_field_type::blob;
case field_kind::float_: return protocol_field_type::float_;
case field_kind::double_: return protocol_field_type::double_;
@ -74,13 +74,18 @@ inline std::size_t boost::mysql::detail::serialization_traits<
get_size(ctx, value.statement_id, value.flags, value.iteration_count);
auto num_params = std::distance(value.params_begin, value.params_end);
assert(num_params >= 0 && num_params <= 255);
res += null_bitmap_traits(stmt_execute_null_bitmap_offset, num_params).byte_count();
res += get_size(ctx, value.new_params_bind_flag);
res += get_size(ctx, com_stmt_execute_param_meta_packet{}) * num_params;
for (auto it = value.params_begin; it != value.params_end; ++it)
if (num_params > 0u)
{
res += get_size(ctx, *it);
res += null_bitmap_traits(stmt_execute_null_bitmap_offset, num_params).byte_count();
res += get_size(ctx, value.new_params_bind_flag);
res += get_size(ctx, com_stmt_execute_param_meta_packet{}) * num_params;
for (auto it = value.params_begin; it != value.params_end; ++it)
{
res += get_size(ctx, *it);
}
}
return res;
}
@ -100,35 +105,38 @@ inline void boost::mysql::detail::serialization_traits<
auto num_params = std::distance(input.params_begin, input.params_end);
assert(num_params >= 0 && num_params <= 255);
// NULL bitmap (already size zero if num_params == 0)
null_bitmap_traits traits(stmt_execute_null_bitmap_offset, num_params);
std::size_t i = 0;
std::memset(ctx.first(), 0, traits.byte_count()); // Initialize to zeroes
for (auto it = input.params_begin; it != input.params_end; ++it, ++i)
if (num_params > 0)
{
if (it->is_null())
// NULL bitmap
null_bitmap_traits traits(stmt_execute_null_bitmap_offset, num_params);
std::size_t i = 0;
std::memset(ctx.first(), 0, traits.byte_count()); // Initialize to zeroes
for (auto it = input.params_begin; it != input.params_end; ++it, ++i)
{
traits.set_null(ctx.first(), i);
if (it->is_null())
{
traits.set_null(ctx.first(), i);
}
}
}
ctx.advance(traits.byte_count());
ctx.advance(traits.byte_count());
// new parameters bind flag
serialize(ctx, input.new_params_bind_flag);
// new parameters bind flag
serialize(ctx, input.new_params_bind_flag);
// value metadata
com_stmt_execute_param_meta_packet meta;
for (auto it = input.params_begin; it != input.params_end; ++it)
{
meta.type = get_protocol_field_type(*it);
meta.unsigned_flag = is_unsigned(*it) ? 0x80 : 0;
serialize(ctx, meta);
}
// value metadata
com_stmt_execute_param_meta_packet meta;
for (auto it = input.params_begin; it != input.params_end; ++it)
{
meta.type = get_protocol_field_type(*it);
meta.unsigned_flag = is_unsigned(*it) ? 0x80 : 0;
serialize(ctx, meta);
}
// actual values
for (auto it = input.params_begin; it != input.params_end; ++it)
{
serialize(ctx, *it);
// actual values
for (auto it = input.params_begin; it != input.params_end; ++it)
{
serialize(ctx, *it);
}
}
}

View File

@ -1622,7 +1622,6 @@ enum class errc : int
};
/**
* \relates errc
* \brief Streams an error code.
*/
inline std::ostream& operator<<(std::ostream&, errc);

View File

@ -26,9 +26,13 @@ inline error_code make_error_code(errc error);
/**
* \brief Additional information about error conditions
* \details Contains an error message describing what happened. Not all error
* \details
* Contains an error message describing what happened. Not all error
* conditions are able to generate this extended information - those that
* can't have an empty error message.
*\n
* \ref message is encoded using the connection's character set. It's generated
* by the server, and may potentially contain user input.
*/
class error_info
{
@ -44,7 +48,7 @@ public:
/// Gets the error message.
const std::string& message() const noexcept { return msg_; }
/// Sets the error message.
// TODO: hide this
void set_message(std::string&& err) { msg_ = std::move(err); }
/// Restores the object to its initial state.

View File

@ -1,31 +0,0 @@
//
// 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)
//
#ifndef BOOST_MYSQL_EXECUTE_OPTIONS_HPP
#define BOOST_MYSQL_EXECUTE_OPTIONS_HPP
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
#include <iterator>
#include <type_traits>
namespace boost {
namespace mysql {
/**
* \brief Additional statement execution options.
* \details Placeholder for now.
*/
class execute_options
{
public:
};
} // namespace mysql
} // namespace boost
#endif

View File

@ -0,0 +1,156 @@
//
// 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)
//
#ifndef BOOST_MYSQL_EXECUTION_STATE_HPP
#define BOOST_MYSQL_EXECUTION_STATE_HPP
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/detail/protocol/common_messages.hpp>
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
#include <boost/utility/string_view.hpp>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace boost {
namespace mysql {
/**
* \brief Holds state for multi-function SQL execution operations.
*/
class execution_state
{
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
public:
#ifndef BOOST_MYSQL_DOXYGEN
// Private, do not use. TODO: hide these
void reset(detail::resultset_encoding encoding) noexcept
{
seqnum_ = 0;
encoding_ = encoding;
meta_.clear();
eof_received_ = false;
}
void complete(const detail::ok_packet& pack)
{
affected_rows_ = pack.affected_rows.value;
last_insert_id_ = pack.last_insert_id.value;
warnings_ = pack.warnings;
info_.assign(pack.info.value.begin(), pack.info.value.end());
eof_received_ = true;
}
void prepare_meta(std::size_t num_fields) { meta_.reserve(num_fields); }
void add_meta(const detail::column_definition_packet& pack) { meta_.emplace_back(pack, true); }
detail::resultset_encoding encoding() const noexcept { return encoding_; }
std::uint8_t& sequence_number() noexcept { return seqnum_; }
std::vector<metadata>& fields() noexcept { return meta_; }
const std::vector<metadata>& fields() const noexcept { return meta_; }
#endif
/**
* \brief Default constructor.
* \details The constructed object is guaranteed to have `meta().empty()` and
* `!complete()`.
*/
execution_state() = default;
/**
* \brief Returns whether the resultset generated by this operation has been completely read.
* \details
* Once `complete`, you may access extra information about the operation, like
* \ref affected_rows or \ref last_insert_id.
*/
bool complete() const noexcept { return eof_received_; }
/**
* \brief Returns metadata about the columns in the query.
* \details
* The returned collection will have as many \ref metadata objects as columns retrieved by
* the SQL query, and in the same order.
*\n
* 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.
*/
metadata_collection_view meta() const noexcept
{
return metadata_collection_view(meta_.data(), meta_.size());
}
/**
* \brief Returns the number of rows affected by the executed SQL statement.
* \details Precondition: `this->complete() == true`.
*/
std::uint64_t affected_rows() const noexcept
{
assert(complete());
return affected_rows_;
}
/**
* \brief Returns the last insert ID produced by the executed SQL statement.
* \details Precondition: `this->complete() == true`.
*/
std::uint64_t last_insert_id() const noexcept
{
assert(complete());
return last_insert_id_;
}
/**
* \brief Returns the number of warnings produced by the executed SQL statement.
* \details Precondition: `this->complete() == true`.
*/
unsigned warning_count() const noexcept
{
assert(complete());
return warnings_;
}
/**
* \brief Returns additionat text information about the execution of the SQL statement.
* \details Precondition: `this->complete() == true`.
*\n
* 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.
*\n
* 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.
*/
boost::string_view info() const noexcept
{
assert(complete());
return boost::string_view(info_.data(), info_.size());
}
};
} // namespace mysql
} // namespace boost
#endif

View File

@ -51,20 +51,28 @@ public:
/// Copy constructor.
field(const field&) = default;
/// Move constructor.
field(field&&) = default;
/**
* \brief Move constructor.
* \details All references into `other` are invalidated, including the ones obtained by calling
* get_xxx, as_xxx and \ref field::operator field_view().
*/
field(field&& other) = default;
/**
* \brief Copy assignment.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
field& operator=(const field&) = default;
/**
* \brief Move assignment.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \details Invalidates references to `*this` obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view(). All references into `other`
* are invalidated, including the ones obtained by calling get_xxx, as_xxx and
* \ref field::operator field_view().
*/
field& operator=(field&&) = default;
field& operator=(field&& other) = default;
/// Destructor.
~field() = default;
@ -158,11 +166,8 @@ public:
field(const field_view& v) { from_view(v); }
/**
* \brief Replaces `*this` with a `NULL`, changing the kind to `null` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_null.
* \copydoc emplace_null
* \details Equivalent to \ref emplace_null.
*/
field& operator=(std::nullptr_t) noexcept
{
@ -171,11 +176,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `int64` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_int64.
* \copydoc emplace_int64
* \details Equivalent to \ref emplace_int64.
*/
field& operator=(signed char v) noexcept
{
@ -212,11 +214,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `uint64` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_uint64.
* \copydoc emplace_uint64
* \details Equivalent to \ref emplace_uint64.
*/
field& operator=(unsigned char v) noexcept
{
@ -253,11 +252,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `string` and destroying any previous
* contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_string.
* \copydoc emplace_string
* \details Equivalent to \ref emplace_string.
*/
field& operator=(const std::string& v)
{
@ -265,31 +261,21 @@ public:
return *this;
}
/**
* \copybrief operator=(const std::string&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_string.
*/
/// \copydoc operator=(const std::string&)
field& operator=(std::string&& v)
{
emplace_string(std::move(v));
return *this;
}
/**
* \copybrief operator=(const std::string&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_string.
*/
/// \copydoc operator=(const std::string&)
field& operator=(const char* v)
{
emplace_string(v);
return *this;
}
/// \copydoc operator=(const char*)
/// \copydoc operator=(const std::string&)
field& operator=(boost::string_view v)
{
emplace_string(v);
@ -297,7 +283,7 @@ public:
}
#if defined(__cpp_lib_string_view) || defined(BOOST_MYSQL_DOXYGEN)
/// \copydoc operator=(const char*)
/// \copydoc operator=(const std::string&)
field& operator=(std::string_view v)
{
emplace_string(v);
@ -306,11 +292,8 @@ public:
#endif
/**
* \brief Replaces `*this` with `v`, changing the kind to `blob` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_blob.
* \copydoc emplace_blob
* \details Equivalent to \ref emplace_blob.
*/
field& operator=(blob v)
{
@ -319,11 +302,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `float_` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_float.
* \copydoc emplace_float
* \details Equivalent to \ref emplace_float.
*/
field& operator=(float v) noexcept
{
@ -332,11 +312,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `double` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_double.
* \copydoc emplace_double
* \details Equivalent to \ref emplace_double.
*/
field& operator=(double v) noexcept
{
@ -345,11 +322,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `date` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_date.
* \copydoc emplace_date
* \details Equivalent to \ref emplace_date.
*/
field& operator=(const date& v) noexcept
{
@ -358,11 +332,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `datetime` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_datetime.
* \copydoc emplace_datetime
* \details Equivalent to \ref emplace_datetime.
*/
field& operator=(const datetime& v) noexcept
{
@ -371,11 +342,8 @@ public:
}
/**
* \brief Replaces `*this` with `v`, changing the kind to `time` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
*\n
* Equivalent to \ref field::emplace_time.
* \copydoc emplace_time
* \details Equivalent to \ref emplace_time.
*/
field& operator=(const time& v) noexcept
{
@ -386,7 +354,8 @@ public:
/**
* \brief Replaces `*this` with `v`, changing the kind to `v.kind()` and destroying any previous
* contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \details Invalidates references to `*this` obtained by as_xxx and get_xxx functions, but not
* the ones obtained by \ref field::operator field_view().
*\n
* `*this` is guaranteed to be valid even after `v` becomes invalid.
*/
@ -646,26 +615,34 @@ public:
time& get_time() noexcept { return repr_.get<time>(); }
/**
* \copybrief operator=(std::nullptr_t)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with a `NULL`, changing the kind to `null` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_null() noexcept { repr_.data.emplace<detail::field_impl::null_t>(); }
/**
* \copybrief operator=(long long)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `int64` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_int64(std::int64_t v) noexcept { repr_.data.emplace<std::int64_t>(v); }
/**
* \copybrief operator=(unsigned long long)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `uint64` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_uint64(std::uint64_t v) noexcept { repr_.data.emplace<std::uint64_t>(v); }
/**
* \copybrief operator=(const std::string&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `string` and destroying any previous
* contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_string(const std::string& v) { repr_.data.emplace<std::string>(v); }
@ -684,38 +661,49 @@ public:
#endif
/**
* \copybrief operator=(blob)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `blob` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_blob(blob v) { repr_.data.emplace<blob>(std::move(v)); }
/**
* \copybrief operator=(float)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `float_` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_float(float v) noexcept { repr_.data.emplace<float>(v); }
/**
* \copybrief operator=(double)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `double` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_double(double v) noexcept { repr_.data.emplace<double>(v); }
/**
* \copybrief operator=(const date&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `date` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_date(const date& v) noexcept { repr_.data.emplace<date>(v); }
/**
* \copybrief operator=(const datetime&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `datetime` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions,
* but not the ones obtained by \ref field::operator field_view().
*/
void emplace_datetime(const datetime& v) noexcept { repr_.data.emplace<datetime>(v); }
/**
* \copybrief operator=(const time&)
* \details Invalidates references obtained by as_xxx and get_xxx functions.
* \brief Replaces `*this` with `v`, changing the kind to `time` and destroying any
* previous contents.
* \details Invalidates references obtained by as_xxx and get_xxx functions, but not
*/
void emplace_time(const time& v) noexcept { repr_.data.emplace<time>(v); }

View File

@ -14,7 +14,7 @@ namespace boost {
namespace mysql {
/**
* \brief Represents the possible C++ types a \ref field or \ref field_view may have.
* \brief Represents the possible C++ types a `field` or `field_view` may have.
*/
enum class field_kind
{
@ -54,7 +54,6 @@ enum class field_kind
};
/**
* \relates field_kind
* \brief Streams a field_kind.
*/
inline std::ostream& operator<<(std::ostream& os, field_kind v);

View File

@ -11,16 +11,20 @@
#pragma once
#include <boost/mysql/connection.hpp>
#include <boost/mysql/detail/network_algorithms/close_connection.hpp>
#include <boost/mysql/detail/network_algorithms/connect.hpp>
#include <boost/mysql/detail/network_algorithms/execute_query.hpp>
#include <boost/mysql/detail/network_algorithms/handshake.hpp>
#include <boost/mysql/detail/network_algorithms/prepare_statement.hpp>
#include <boost/mysql/detail/network_algorithms/quit_connection.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/network_algorithms/close_connection.hpp>
#include <boost/mysql/detail/network_algorithms/connect.hpp>
#include <boost/mysql/detail/network_algorithms/handshake.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_one_row.hpp>
#include <boost/mysql/detail/network_algorithms/read_some_rows.hpp>
#include <boost/mysql/detail/network_algorithms/start_query.hpp>
#include <boost/asio/buffer.hpp>
#include <utility>
@ -109,27 +113,65 @@ boost::mysql::connection<Stream>::async_handshake(
);
}
// Query
template <class Stream>
void boost::mysql::connection<Stream>::query(
void boost::mysql::connection<Stream>::start_query(
boost::string_view query_string,
resultset<Stream>& result,
execution_state& result,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
detail::execute_query(get_channel(), query_string, result, err, info);
detail::start_query(get_channel(), query_string, result, err, info);
}
template <class Stream>
void boost::mysql::connection<Stream>::start_query(
boost::string_view query_string,
execution_state& result
)
{
detail::error_block blk;
detail::start_query(get_channel(), query_string, result, blk.err, blk.info);
blk.check();
}
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_start_query(
boost::string_view query_string,
execution_state& result,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_start_query(
get_channel(),
query_string,
result,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
void boost::mysql::connection<Stream>::query(
boost::string_view query_string,
resultset<Stream>& result
resultset& result,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
detail::query(get_channel(), query_string, result, err, info);
}
template <class Stream>
void boost::mysql::connection<Stream>::query(boost::string_view query_string, resultset& result)
{
detail::error_block blk;
detail::execute_query(get_channel(), query_string, result, blk.err, blk.info);
detail::query(get_channel(), query_string, result, blk.err, blk.info);
blk.check();
}
@ -138,12 +180,12 @@ template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) Comp
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::connection<Stream>::async_query(
boost::string_view query_string,
resultset<Stream>& result,
resultset& result,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_execute_query(
return detail::async_query(
get_channel(),
query_string,
result,
@ -250,4 +292,87 @@ boost::mysql::connection<Stream>::async_quit(error_info& output_info, Completion
);
}
template <class Stream>
boost::mysql::row_view boost::mysql::connection<Stream>::read_one_row(
execution_state& st,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
return detail::read_one_row(get_channel(), st, err, info);
}
template <class Stream>
boost::mysql::row_view boost::mysql::connection<Stream>::read_one_row(execution_state& st)
{
detail::error_block blk;
row_view res = detail::read_one_row(get_channel(), st, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view)
) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::row_view)
)
boost::mysql::connection<Stream>::async_read_one_row(
execution_state& st,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_one_row(
get_channel(),
st,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
boost::mysql::rows_view boost::mysql::connection<Stream>::read_some_rows(
execution_state& st,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
return detail::read_some_rows(get_channel(), st, err, info);
}
template <class Stream>
boost::mysql::rows_view boost::mysql::connection<Stream>::read_some_rows(execution_state& st)
{
detail::error_block blk;
rows_view res = detail::read_some_rows(get_channel(), st, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::rows_view)
)
boost::mysql::connection<Stream>::async_read_some_rows(
execution_state& st,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_some_rows(
get_channel(),
st,
output_info,
std::forward<CompletionToken>(token)
);
}
#endif

View File

@ -1,246 +0,0 @@
//
// 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)
//
#ifndef BOOST_MYSQL_IMPL_RESULTSET_HPP
#define BOOST_MYSQL_IMPL_RESULTSET_HPP
#pragma once
#include <boost/mysql/detail/network_algorithms/read_all_rows.hpp>
#include <boost/mysql/detail/network_algorithms/read_one_row.hpp>
#include <boost/mysql/detail/network_algorithms/read_some_rows.hpp>
#include <boost/mysql/resultset.hpp>
// Read one row
template <class Stream>
boost::mysql::row_view boost::mysql::resultset<Stream>::read_one(
use_views_t,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
return detail::read_one_row(get_channel(), *this, err, info);
}
template <class Stream>
boost::mysql::row_view boost::mysql::resultset<Stream>::read_one(use_views_t)
{
detail::error_block blk;
row_view res = detail::read_one_row(get_channel(), *this, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view)
) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::row_view)
)
boost::mysql::resultset<Stream>::async_read_one(
use_views_t,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_one_row(
get_channel(),
*this,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
bool boost::mysql::resultset<Stream>::read_one(row& output, error_code& err, error_info& info)
{
detail::clear_errors(err, info);
return detail::read_one_row(get_channel(), *this, output, err, info);
}
template <class Stream>
bool boost::mysql::resultset<Stream>::read_one(row& output)
{
detail::error_block blk;
bool res = detail::read_one_row(get_channel(), *this, output, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, bool)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, bool))
boost::mysql::resultset<Stream>::async_read_one(
row& output,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_one_row(
get_channel(),
*this,
output,
output_info,
std::forward<CompletionToken>(token)
);
}
// Read some rows
template <class Stream>
boost::mysql::rows_view boost::mysql::resultset<Stream>::read_some(
use_views_t,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
return detail::read_some_rows(get_channel(), *this, err, info);
}
template <class Stream>
boost::mysql::rows_view boost::mysql::resultset<Stream>::read_some(use_views_t)
{
detail::error_block blk;
rows_view res = detail::read_some_rows(get_channel(), *this, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::rows_view)
)
boost::mysql::resultset<Stream>::async_read_some(
use_views_t,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_some_rows(
get_channel(),
*this,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
void boost::mysql::resultset<Stream>::read_some(rows& output, error_code& err, error_info& info)
{
detail::clear_errors(err, info);
detail::read_some_rows(get_channel(), *this, output, err, info);
}
template <class Stream>
void boost::mysql::resultset<Stream>::read_some(rows& output)
{
detail::error_block blk;
detail::read_some_rows(get_channel(), *this, output, blk.err, blk.info);
blk.check();
}
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::resultset<Stream>::async_read_some(
rows& output,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_some_rows(
get_channel(),
*this,
output,
output_info,
std::forward<CompletionToken>(token)
);
}
// Read all rows
template <class Stream>
boost::mysql::rows_view boost::mysql::resultset<Stream>::read_all(
use_views_t,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
return detail::read_all_rows(get_channel(), *this, err, info);
}
template <class Stream>
boost::mysql::rows_view boost::mysql::resultset<Stream>::read_all(use_views_t)
{
detail::error_block blk;
rows_view res = detail::read_all_rows(get_channel(), *this, blk.err, blk.info);
blk.check();
return res;
}
template <class Stream>
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, boost::mysql::rows_view)
)
boost::mysql::resultset<Stream>::async_read_all(
use_views_t,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_all_rows(
get_channel(),
*this,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
void boost::mysql::resultset<Stream>::read_all(rows& output, error_code& err, error_info& info)
{
detail::clear_errors(err, info);
detail::read_all_rows(get_channel(), *this, output, err, info);
}
template <class Stream>
void boost::mysql::resultset<Stream>::read_all(rows& output)
{
detail::error_block blk;
detail::read_all_rows(get_channel(), *this, output, blk.err, blk.info);
blk.check();
}
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::resultset<Stream>::async_read_all(
rows& output,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_read_all_rows(
get_channel(),
*this,
output,
output_info,
std::forward<CompletionToken>(token)
);
}
#endif

View File

@ -10,10 +10,11 @@
#pragma once
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
#include <boost/mysql/rows.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
#include <cassert>
#include <cstddef>
#include <stdexcept>
@ -30,13 +31,12 @@ boost::mysql::rows& boost::mysql::rows::operator=(const rows_view& rhs)
if (rhs.fields_ == fields_.data())
{
assert(rhs.num_fields_ == fields_.size());
assert(rhs.num_columns() == num_columns());
}
else
{
assign(rhs.fields_, rhs.num_fields_);
num_columns_ = rhs.num_columns_;
}
num_columns_ = rhs.num_columns_;
return *this;
}

View File

@ -10,35 +10,31 @@
#pragma once
#include <boost/mysql/detail/network_algorithms/close_statement.hpp>
#include <boost/mysql/detail/network_algorithms/execute_statement.hpp>
#include <boost/mysql/statement.hpp>
// Execute statement, with tuple
#include <boost/mysql/detail/network_algorithms/close_statement.hpp>
#include <boost/mysql/detail/network_algorithms/execute_statement.hpp>
#include <boost/mysql/detail/network_algorithms/start_statement_execution.hpp>
template <class Stream>
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
void boost::mysql::statement<Stream>::execute(
const FieldLikeTuple& params,
const execute_options& opts,
resultset<Stream>& result,
resultset& result,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
detail::execute_statement(get_channel(), *this, params, opts, result, err, info);
detail::execute_statement(get_channel(), *this, params, result, err, info);
}
template <class Stream>
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
void boost::mysql::statement<Stream>::execute(
const FieldLikeTuple& params,
const execute_options& opts,
resultset<Stream>& result
)
void boost::mysql::statement<Stream>::execute(const FieldLikeTuple& params, resultset& result)
{
detail::error_block blk;
detail::execute_statement(get_channel(), *this, params, opts, result, blk.err, blk.info);
detail::execute_statement(get_channel(), *this, params, result, blk.err, blk.info);
blk.check();
}
@ -50,8 +46,7 @@ template <
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::statement<Stream>::async_execute(
FieldLikeTuple&& params,
const execute_options& opts,
resultset<Stream>& result,
resultset& result,
error_info& output_info,
CompletionToken&& token
)
@ -60,7 +55,54 @@ boost::mysql::statement<Stream>::async_execute(
get_channel(),
*this,
std::forward<FieldLikeTuple>(params),
opts,
result,
output_info,
std::forward<CompletionToken>(token)
);
}
template <class Stream>
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
void boost::mysql::statement<Stream>::start_execution(
const FieldLikeTuple& params,
execution_state& result,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
detail::start_statement_execution(get_channel(), *this, params, result, err, info);
}
template <class Stream>
template <BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple, class>
void boost::mysql::statement<Stream>::start_execution(
const FieldLikeTuple& params,
execution_state& result
)
{
detail::error_block blk;
detail::start_statement_execution(get_channel(), *this, params, result, blk.err, blk.info);
blk.check();
}
template <class Stream>
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken,
class>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
boost::mysql::statement<Stream>::async_start_execution(
FieldLikeTuple&& params,
execution_state& result,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_start_statement_execution(
get_channel(),
*this,
std::forward<FieldLikeTuple>(params),
result,
output_info,
std::forward<CompletionToken>(token)
@ -70,22 +112,20 @@ boost::mysql::statement<Stream>::async_execute(
// Execute statement, with iterators
template <class Stream>
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void boost::mysql::statement<Stream>::execute(
void boost::mysql::statement<Stream>::start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& opts,
resultset<Stream>& result,
execution_state& result,
error_code& err,
error_info& info
)
{
detail::clear_errors(err, info);
detail::execute_statement(
detail::start_statement_execution(
get_channel(),
*this,
params_first,
params_last,
opts,
result,
err,
info
@ -94,20 +134,18 @@ void boost::mysql::statement<Stream>::execute(
template <class Stream>
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void boost::mysql::statement<Stream>::execute(
void boost::mysql::statement<Stream>::start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& opts,
resultset<Stream>& result
execution_state& result
)
{
detail::error_block blk;
detail::execute_statement(
detail::start_statement_execution(
get_channel(),
*this,
params_first,
params_last,
opts,
result,
blk.err,
blk.info
@ -120,21 +158,19 @@ template <
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::statement<Stream>::async_execute(
boost::mysql::statement<Stream>::async_start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& opts,
resultset<Stream>& result,
execution_state& result,
error_info& output_info,
CompletionToken&& token
)
{
return detail::async_execute_statement(
return detail::async_start_statement_execution(
get_channel(),
*this,
params_first,
params_last,
opts,
result,
output_info,
std::forward<CompletionToken>(token)

View File

@ -9,6 +9,7 @@
#define BOOST_MYSQL_METADATA_HPP
#include <boost/mysql/column_type.hpp>
#include <boost/mysql/detail/auxiliar/bytestring.hpp>
#include <boost/mysql/detail/protocol/common_messages.hpp>
@ -50,7 +51,7 @@ public:
/**
* \brief Move constructor.
* \details Any `string_view`s obtained by calling accessor functions on `other` remain valid.
* \details `string_view`s obtained by calling accessor functions on `other` are invalidated.
*/
metadata(metadata&& other) = default;
@ -59,15 +60,15 @@ public:
/**
* \brief Move assignment.
* \details Any `string_view`s obtained by calling accessor functions on `other` remain valid.
* Any `string_view`s pointing into `*this` are invalidated.
* \details `string_view`s obtained by calling accessor functions on both `*this` and `other`
* are invalidated.
*/
metadata& operator=(metadata&& other) = default;
/**
* \brief Copy assignment.
* \details Any `string_view`s obtained by calling accessor functions on `*this` are
* invalidated.
* \details `string_view`s obtained by calling accessor functions on `*this`
* are invalidated.
*/
metadata& operator=(const metadata& other) = default;
@ -124,7 +125,12 @@ public:
*/
boost::string_view original_column_name() const noexcept { return org_name_; }
/// Returns the \ref collation of the column.
/**
* \brief Returns the collation that fields belonging to this column use.
* \details This collation matches the character set and collation that
* fields belonging to this column use, rather than the collation used to
* define the column. It will almost always match the connection's collation.
*/
collation column_collation() const noexcept { return character_set_; }
/// Returns the maximum length of the column.

View File

@ -8,14 +8,10 @@
#ifndef BOOST_MYSQL_RESULTSET_HPP
#define BOOST_MYSQL_RESULTSET_HPP
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/resultset_base.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/rows.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/use_views.hpp>
#include <boost/asio/async_result.hpp>
#include <cassert>
@ -23,378 +19,143 @@ namespace boost {
namespace mysql {
/**
* \brief Holds state and data about a query or statement execution, and allows reading the produced
* rows.
* \details
* Resultsets are obtained as a result of a SQL statement execution, by calling \ref
* connection::query or \ref statement::execute. A resultset holds metadata about the operation
* (\ref meta), allows you to read the resulting rows ( \ref read_one, \ref read_some, \ref
* read_all), and stores additional information about the execution ("EOF packet data", in MySQL
* slang: \ref affected_rows, \ref last_insert_id, and so on). Resultsets are the glue that join
* the different pieces of \ref connection::query and \ref statement::execute multi-function
* operations.
* \n
* Resultsets are proxy I/O objects, meaning that they hold a reference to the internal state of the
* \ref connection that created them. I/O operations on a resultset result in reads and writes on
* the connection's stream. A `resultset` is usable for I/O operations as long as the original \ref
* connection is alive and open. Moving the `connection` object doesn't invalidate `resultset`s
* pointing into it, but destroying or closing it does. The executor object used by a `resultset` is
* always the underlying stream's.
* \n
* Resultsets are default-constructible and movable, but not copyable. A default constructed or
* closed resultset has `!this->valid()`. Calling any member function on an invalid
* resultset, other than assignment, results in undefined behavior.
* \brief Holds the results of a SQL query.
*/
template <class Stream>
class resultset : public resultset_base
class resultset
{
public:
// TODO: hide these
execution_state& state() noexcept { return st_; }
::boost::mysql::rows& mutable_rows() noexcept { return rows_; }
/**
* \brief Default constructor.
* \details Default constructed resultsets have `!this->valid()`.
* \details Constructs an empty resultset, with `this->has_value() == false`.
*/
resultset() = default;
#ifndef BOOST_MYSQL_DOXYGEN
resultset(const resultset&) = delete;
resultset& operator=(const resultset&) = delete;
#endif
/**
* \brief Copy constructor.
*/
resultset(const resultset& other) = default;
/**
* \brief Move constructor.
* \details Views obtained from `other.meta()` and `other.info()` remain valid.
* \details View objects referencing `other` remain valid.
*/
resultset(resultset&& other) noexcept : resultset_base(std::move(other)) { other.reset(); }
resultset(resultset&& other) = default;
/**
* \brief Copy assignment.
* \details View objects referencing `*this` are invalidated.
*/
resultset& operator=(const resultset& other) = default;
/**
* \brief Move assignment.
* \details Views obtained from `other.meta()` and `other.info()` remain valid. Views obtained
* from `this->meta()` and `this->info()` are invalidated.
* \details View objects referencing `other` remain valid. View objects
* referencing `*this` are invalidated.
*/
resultset& operator=(resultset&& other) noexcept
{
swap(other);
other.reset();
return *this;
}
resultset& operator=(resultset&& other) = default;
/**
* \brief Destructor.
* \details Views obtained from `this->meta()` and `this->info()` are invalidated.
*/
/// Destructor
~resultset() = default;
/// The executor type associated to this object.
using executor_type = typename Stream::executor_type;
/// Retrieves the executor associated to this object.
executor_type get_executor() { return get_channel().get_executor(); }
/**
* \brief Returns whether the object holds a valid result.
* \details Having `this->has_value()` is a precondition to call all data accessors.
* Objects populated by \ref connection::query, \ref statement::execute or their async
* counterparts are guaranteed to have `this->has_value()`.
*/
bool has_value() const noexcept { return st_.complete(); }
/**
* \brief Reads a single row.
* \details
* Returns `true` if a row was read successfully. If there were no more rows to read,
* it returns `false` and sets `output` to an empty row.
* \brief Returns the rows retrieved by the SQL query.
* \details Precondition: `this->has_value()`.
*\n
* Use this operation to read large resultsets that may not entirely fit in memory, or when
* individual row processing is preferred.
* 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.
*/
bool read_one(row& output, error_code& err, error_info& info);
/// \copydoc read_one(row&,error_code&,error_info&)
bool read_one(row& output);
/**
* \copydoc read_one(row&,error_code&,error_info&)
* \details
* The handler signature for this operation is
* `void(boost::mysql::error_code, bool)`.
*/
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, bool))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool))
async_read_one(
row& output,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
rows_view rows() const noexcept
{
return async_read_one(
output,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return rows_;
}
/// \copydoc async_read_one(row&,CompletionToken&&)
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, bool))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool))
async_read_one(
row& output,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Reads a single row as a \ref row_view.
* \brief Returns metadata about the columns in the query.
* \details
* If a row was read successfully, returns a non-empty \ref row_view.
* If there were no more rows to read, returns an empty `row_view`.
* Precondition: `this->has_value()`.
*\n
* The returned view points into the \ref connection that `*this` references.
* It will be valid until the `connection` performs any other read operation
* or is destroyed.
* The returned collection will have as many \ref metadata objects as columns retrieved by
* the SQL query, and in the same order.
*\n
* 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.
*/
row_view read_one(use_views_t, error_code& err, error_info& info);
/// \copydoc read_one(use_views_t,error_code&,error_info&)
row_view read_one(use_views_t);
/**
* \copydoc read_one(use_views_t,error_code&,error_info&)
*
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::row_view)`.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, row_view))
async_read_one(
use_views_t,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
metadata_collection_view meta() const noexcept
{
return async_read_one(
use_views,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return st_.meta();
}
/// \copydoc async_read_one(use_views_t,CompletionToken&&)
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::row_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, row_view))
async_read_one(
use_views_t,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Reads a batch of rows.
* \details
* The number of rows that will be read is unspecified. The contents of `output` will be
* replaced by the read rows, so you can obtain the number of read rows using `output.size()`.
* If this resultset has still rows to read, at least one will be read. If there are no more
* rows to be read, `output` will be `empty()`.
* \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 \ref
* connection::connect, using \ref buffer_params::initial_read_buffer_size. The buffer may be
* grown bigger if required by other read operations.
* \n
* This is the most complex but most performant way of reading rows. You may consider
* the overload returning a \ref rows_view object, too, which performs less copying.
* \brief Returns the number of rows affected by the executed SQL statement.
* \details Precondition: `this->has_value() == true`.
*/
void read_some(rows& output, error_code& err, error_info& info);
/// \copydoc read_some(rows&,error_code&,error_info&)
void read_some(rows& output);
/**
* \copydoc read_some(rows&,error_code&,error_info&)
* \details
* 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_some(
rows& output,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
std::uint64_t affected_rows() const noexcept
{
return async_read_some(
output,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return st_.affected_rows();
}
/// \copydoc async_read_some(rows&,CompletionToken&&)
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_some(
rows& output,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Reads a batch of rows as a \ref rows_view.
* \details
* The number of rows that will be read is unspecified. The contents of `output` will be
* replaced by the read rows, so you can obtain the number of read rows using `output.size()`.
* If this resultset has still rows to read, at least one will be read. If there are no more
* rows to be read, `output` will be `empty()`.
* \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 \ref
* connection::connect, using \ref buffer_params::initial_read_buffer_size. The buffer may be
* grown bigger if required by other read operations.
* \n
* The returned view points into the \ref connection that `*this` references.
* It will be valid until the `connection` performs any other read operation
* or is destroyed.
* \n
* This is the most complex but most performant way of reading rows.
* \brief Returns the last insert ID produced by the executed SQL statement.
* \details Precondition: `this->has_value() == true`.
*/
rows_view read_some(use_views_t, error_code& err, error_info& info);
/// \copydoc read_some(use_views_t,error_code&,error_info&)
rows_view read_some(use_views_t);
/**
* \copydoc read_some(use_views_t,error_code&,error_info&)
* \details
* The handler signature for this operation is
* `void(boost::mysql::error_code, boost::mysql::rows_view)`.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some(
use_views_t,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
std::uint64_t last_insert_id() const noexcept
{
return async_read_some(
use_views,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return st_.last_insert_id();
}
/// \copydoc async_read_some(use_views_t,CompletionToken&&)
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some(
use_views_t,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Reads all remaining rows.
* \details
* The contents of `output` will be replaced by the read rows, so you can obtain the number of
* read rows using `output.size()`. After this operation succeeds, `this->complete() == true`.
* \n
* This function requires fitting all the rows in the resultset into memory. It is a good choice
* for small resultsets.
* \brief Returns the number of warnings produced by the executed SQL statement.
* \details Precondition: `this->has_value() == true`.
*/
void read_all(rows& output, error_code& err, error_info& info);
/// \copydoc read_all(rows&,error_code&,error_info&)
void read_all(rows& output);
/**
* \copydoc read_all(rows&,error_code&,error_info&)
* 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_all(
rows& output,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
unsigned warning_count() const noexcept
{
return async_read_all(
output,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return st_.warning_count();
}
/// \copydoc async_read_all(rows&,CompletionToken&&)
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_all(
rows& output,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Reads all remaining rows.
* \details
* The contents of `output` will be replaced by the read rows, so you can obtain the number of
* read rows using `output.size()`. After this operation succeeds, `this->complete() == true`.
* \n
* This function requires fitting all the rows in the resultset into memory. It is a good choice
* for small resultsets.
* \n
* The returned view points into the \ref connection that `*this` references.
* It will be valid until the `connection` performs any other read operation
* or is destroyed.
* \brief Returns additionat text information about the execution of the SQL statement.
* \details Precondition: `this->has_value() == true`.
*\n
* 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.
*\n
* 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.
*/
rows_view read_all(use_views_t, error_code& err, error_info& info);
/// \copydoc read_all(use_views_t,error_code&,error_info&)
rows_view read_all(use_views_t);
/**
* \copydoc read_all(use_views_t,error_code&,error_info&)
* \details
* The handler signature for this operation is `void(boost::mysql::error_code)`.
*/
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_all(
use_views_t,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
boost::string_view info() const noexcept
{
return async_read_all(
use_views,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
assert(has_value());
return st_.info();
}
/// \copydoc async_read_all(use_views_t,CompletionToken&&)
template <
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code, ::boost::mysql::rows_view))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_all(
use_views_t,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
private:
detail::channel<Stream>& get_channel() noexcept
{
assert(valid());
return *static_cast<detail::channel<Stream>*>(channel_ptr());
}
execution_state st_;
::boost::mysql::rows rows_;
};
} // namespace mysql
} // namespace boost
#include <boost/mysql/impl/resultset.hpp>
#endif

View File

@ -1,195 +0,0 @@
//
// 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)
//
#ifndef BOOST_MYSQL_RESULTSET_BASE_HPP
#define BOOST_MYSQL_RESULTSET_BASE_HPP
#include <boost/mysql/detail/protocol/common_messages.hpp>
#include <boost/mysql/detail/protocol/deserialization_context.hpp>
#include <boost/mysql/detail/protocol/resultset_encoding.hpp>
#include <boost/mysql/error.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/row.hpp>
#include <boost/utility/string_view.hpp>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace boost {
namespace mysql {
/**
* \brief The base class for resultsets.
* \details Don't instantiate this class directly - use \ref resultset instead.
*\n
* All member functions, except otherwise noted, have `this->valid()` as precondition.
* Calling any function on an invalid resultset results in undefined behavior.
*/
class resultset_base
{
class ok_packet_data
{
bool has_data_{false};
std::uint64_t affected_rows_;
std::uint64_t last_insert_id_;
std::uint16_t warnings_;
std::string info_;
public:
ok_packet_data() = default;
ok_packet_data(const detail::ok_packet& pack) { assign(pack); }
void reset() noexcept { has_data_ = false; }
void assign(const detail::ok_packet& pack)
{
has_data_ = true;
affected_rows_ = pack.affected_rows.value;
last_insert_id_ = pack.last_insert_id.value;
warnings_ = pack.warnings;
info_.assign(pack.info.value.begin(), pack.info.value.end());
}
bool has_value() const noexcept { return has_data_; }
std::uint64_t affected_rows() const noexcept
{
assert(has_data_);
return affected_rows_;
}
std::uint64_t last_insert_id() const noexcept
{
assert(has_data_);
return last_insert_id_;
}
unsigned warning_count() const noexcept
{
assert(has_data_);
return warnings_;
}
boost::string_view info() const noexcept
{
assert(has_data_);
return info_;
}
};
void* channel_{nullptr};
std::uint8_t seqnum_{};
detail::resultset_encoding encoding_{detail::resultset_encoding::text};
std::vector<metadata> meta_;
ok_packet_data ok_packet_;
public:
#ifndef BOOST_MYSQL_DOXYGEN
// Private, do not use. TODO: hide these
resultset_base() = default;
void reset(void* channel, detail::resultset_encoding encoding) noexcept
{
channel_ = channel;
seqnum_ = 0;
encoding_ = encoding;
meta_.clear();
ok_packet_.reset();
}
void complete(const detail::ok_packet& ok_pack)
{
assert(valid());
ok_packet_.assign(ok_pack);
}
void prepare_meta(std::size_t num_fields) { meta_.reserve(num_fields); }
void add_meta(const detail::column_definition_packet& pack) { meta_.emplace_back(pack, true); }
detail::resultset_encoding encoding() const noexcept { return encoding_; }
std::uint8_t& sequence_number() noexcept { return seqnum_; }
std::vector<metadata>& fields() noexcept { return meta_; }
const std::vector<metadata>& fields() const noexcept { return meta_; }
#endif
/**
* \brief Returns `true` if the object represents an actual resultset.
* \details Calling any function other than assignment on a resultset for which
* this function returns `false` results in undefined behavior.
*
* To be usable for server communication, the \ref connection referenced by this object must be
* alive and open, too.
*
* Returns `false` for default-constructed and moved-from objects.
*/
bool valid() const noexcept { return channel_ != nullptr; }
/**
* \brief Returns whether the resultset has been completely read or not.
* \details
* After a resultset is `complete`, you may access extra information about the operation, like
* \ref affected_rows or \ref last_insert_id.
*/
bool complete() const noexcept { return ok_packet_.has_value(); }
/**
* \brief Returns metadata about the columns in the query.
* \details
* The returned collection will have as many \ref metadata objects as columns retrieved by
* the SQL query, and in the same order.
*\n
* This function returns a view object, with reference semantics. This view object references
* `*this` internal state, and will be valid as long as `*this` (or a `resultset`
* move-constructed from `*this`) is alive.
*/
metadata_collection_view meta() const noexcept
{
return metadata_collection_view(meta_.data(), meta_.size());
}
/**
* \brief The number of rows affected by the SQL statement that generated this resultset.
* \details The resultset **must be complete**
* before calling this function.
*/
std::uint64_t affected_rows() const noexcept { return ok_packet_.affected_rows(); }
/**
* \brief The last insert ID produced by the SQL statement that generated this resultset.
* \details The resultset **must be complete** before calling this function.
*/
std::uint64_t last_insert_id() const noexcept { return ok_packet_.last_insert_id(); }
/**
* \brief The number of warnings produced by the SQL statement that generated this resultset.
* \details The resultset **must be complete** before calling this function.
*/
unsigned warning_count() const noexcept { return ok_packet_.warning_count(); }
/**
* \brief Additionat text information about the execution of
* the SQL statement that generated this resultset.
* \details The resultset **must be complete** before calling this function.
*\n
* This function returns a view object, with reference semantics. This view object references
* `*this` internal state, and will be valid as long as `*this` (or a `resultset`
* move-constructed from `*this`) is alive.
*/
boost::string_view info() const noexcept { return ok_packet_.info(); }
protected:
void* channel_ptr() noexcept { return channel_; }
void reset() noexcept { reset(nullptr, detail::resultset_encoding::text); }
void swap(resultset_base& other) noexcept { std::swap(*this, other); }
};
} // namespace mysql
} // namespace boost
#endif

View File

@ -8,11 +8,12 @@
#ifndef BOOST_MYSQL_ROW_HPP
#define BOOST_MYSQL_ROW_HPP
#include <boost/mysql/detail/auxiliar/row_base.hpp>
#include <boost/mysql/field.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/detail/auxiliar/row_base.hpp>
#include <cstddef>
#include <iosfwd>
#include <vector>
@ -28,12 +29,9 @@ 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.
* \n
* Objects of this type can be populated by the \ref resultset::read_one function family. You may
* create a `row` directly to persist a \ref row_view, too.
* \n
* Although owning, `row` is read-only.
* It's optimized for memory re-use in \ref resultset::read_one loops. If you need to mutate fields,
* use a `std::vector<field>` instead (see \ref row_view::as_vector and \ref row::as_vector).
* Although owning, `row` is read-only. It's optimized for memory re-use. If you need to mutate
* fields, use a `std::vector<field>` instead (see \ref row_view::as_vector and \ref
* row::as_vector).
*/
class row : private detail::row_base
{

View File

@ -25,15 +25,14 @@ namespace mysql {
* A `row_view` points to memory owned by an external entity (like `string_view` does). The validity
* of a `row_view` depends on how it was obtained:
* \n
* - If it was constructed from a \ref row object (by calling \ref row::operator row_view()), the
* view acts as a reference to the row's allocated memory, and is valid as long as references
* to that row element's are valid.
* - If it was obtained by indexing a \ref rows object, the same applies.
* - If it was obtained by indexing a \ref rows_view object, it's valid as long as the
* \li If it was constructed from a \ref row object (by calling \ref row::operator row_view()), the
* view acts as a reference to the row's allocated memory, and is valid as long as references
* to that row element's are valid.
* \li If it was obtained by indexing a \ref rows object, the same applies.
* \li If it was obtained by indexing a \ref rows_view object, it's valid as long as the
* `rows_view` is valid.
* - If it was obtained by a call to `resultset::read_xxx` or similar functions taking a \ref
* use_views_t parameter, it's valid until the underlying \ref connection performs the next
* network call or is destroyed.
* \li If it was obtained by a call to \ref connection::read_one_row, it's valid until the
* `connection` performs the next network call or is destroyed.
* \n
* Calling any member function on an invalid view results in undefined behavior.
* \n

View File

@ -8,13 +8,14 @@
#ifndef BOOST_MYSQL_ROWS_HPP
#define BOOST_MYSQL_ROWS_HPP
#include <boost/mysql/detail/auxiliar/row_base.hpp>
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/auxiliar/row_base.hpp>
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
namespace boost {
namespace mysql {
@ -24,19 +25,14 @@ namespace mysql {
* Models an owning, matrix-like container. Indexing a `rows` object (by using iterators,
* \ref rows::at or \ref rows::operator[]) returns a \ref row_view object, representing a
* single row. All rows in the collection are the same size (as given by \ref num_columns).
*
*\n
* A `rows` object owns a chunk of memory in which it stores its elements. The \ref rows_view
* objects obtained on element access point into the `rows`' internal storage. These views (and any
* \ref row_view and \ref field_view obtained from the former) behave
* like references, and are valid as long as pointers, iterators and references into the `rows`
* object remain valid.
\n
* Objects of this type can be populated by the \ref resultset::read_some and \ref
* resultset::read_all function family. You may create a `rows` object directly to persist \ref
* rows_view objects, too.
\n
* Although owning, `rows` is read-only. It's optimized for memory re-use in \ref
* resultset::read_some and \ref resultset::read_all loops.
* Although owning, `rows` is read-only. It's optimized for memory re-use.
*/
class rows : private detail::row_base
{

View File

@ -8,11 +8,12 @@
#ifndef BOOST_MYSQL_ROWS_VIEW_HPP
#define BOOST_MYSQL_ROWS_VIEW_HPP
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/row.hpp>
#include <boost/mysql/row_view.hpp>
#include <boost/mysql/detail/auxiliar/rows_iterator.hpp>
#include <cstddef>
namespace boost {
@ -26,13 +27,12 @@ namespace mysql {
* single row. All rows in the collection are the same size (as given by \ref num_columns).
* \n
* A `rows_view` object points to memory owned by an external entity (like `string_view` does). The
* validity of a `rows_view` object depends on how it was obtained: \n
* - If it was constructed from a \ref rows object (by calling \ref rows::operator rows_view()),
* the view acts as a reference to the `rows`' allocated memory, and is valid as long as references
* to that `rows` element's are valid.
* - If it was obtained by a call to `resultset::read_xxx` or similar functions taking a \ref
* use_views_t parameter, it's valid until the underlying \ref connection performs the next
* network call or is destroyed.
* validity of a `rows_view` object depends on how it was obtained:
* \li If it was constructed from a \ref rows object (by calling \ref rows::operator rows_view()),
* the view acts as a reference to the `rows`' allocated memory, and is valid as long as
* references to that `rows` element's are valid.
* \li If it was obtained by calling \ref connection::read_some_rows it's valid until the
* `connection` performs the next network call or is destroyed.
* \n
* \ref row_view's and \ref field_view's obtained by using a `rows_view` object are valid as long as
* the underlying storage that `*this` points to is valid. Destroying `*this` doesn't invalidate
@ -150,10 +150,12 @@ public:
#ifndef BOOST_MYSQL_DOXYGEN
// TODO: hide this
rows_view(const field_view* fields, std::size_t num_values, std::size_t num_columns) noexcept
: fields_(fields), num_fields_(num_values), num_columns_(num_columns)
rows_view(const field_view* fields, std::size_t num_fields, std::size_t num_columns) noexcept
: fields_(fields), num_fields_(num_fields), num_columns_(num_columns)
{
assert(num_values % num_columns == 0);
assert(fields != nullptr || num_fields == 0); // fields null => num_fields 0
assert(num_fields == 0 || num_columns != 0); // num_fields != 0 => num_columns != 0
assert(num_columns == 0 || (num_fields % num_columns == 0));
}
#endif

View File

@ -8,12 +8,13 @@
#ifndef BOOST_MYSQL_STATEMENT_HPP
#define BOOST_MYSQL_STATEMENT_HPP
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <boost/mysql/execute_options.hpp>
#include <boost/mysql/execution_state.hpp>
#include <boost/mysql/resultset.hpp>
#include <boost/mysql/statement_base.hpp>
#include <boost/mysql/detail/auxiliar/field_type_traits.hpp>
#include <boost/mysql/detail/channel/channel.hpp>
#include <cassert>
#include <iterator>
@ -23,15 +24,11 @@ namespace mysql {
/**
* \brief Represents a server-side prepared statement.
* \details
* You can obtain a `valid` statement by calling \ref connection::prepare_statement. You can execute
* a statement using \ref statement::execute, and close it using \ref statement::close.
*\n
* Statements are proxy I/O objects, meaning that they hold a reference to the internal state of the
* \ref connection that created them. I/O operations on a statement result in reads and writes on
* the connection's stream. A `statement` is usable for I/O operations as long as the original \ref
* connection is alive and open. Moving the `connection` object doesn't invalidate `statement`s
* pointing into it, but destroying or closing it does. The executor object used by a `statement` is
* always the underlying stream's.
* the connection's stream. A `statement` is usable for I/O operations as long as the \ref
* connection that created them (or a connection move-constructed from it) is alive and open.
* The executor object used by a `statement` is always the underlying stream's.
*\n
* Statements are default-constructible and movable, but not copyable. A default constructed or
* closed statement has `!this->valid()`. Calling any member function on an invalid
@ -43,8 +40,7 @@ class statement : public statement_base
public:
/**
* \brief Default constructor.
* \details Default constructed statements have `!this->valid()`. To obtain a valid statement,
* call \ref connection::prepare_statement.
* \details Default constructed statements have `this->valid() == false`.
*/
statement() = default;
@ -80,22 +76,16 @@ public:
executor_type get_executor() { return get_channel().get_executor(); }
/**
* \brief Executes a prepared statement, passing parameters as a tuple.
* \brief Executes a prepared statement.
* \details
* Starts a multi-function operation. This function will write the execute request to the
* server and read the initial server response, but won't read the generated rows, if any. After
* this operation completes, `result` will have \ref resultset::meta populated, and may become
* \ref resultset::complete, if the operation did not generate any rows (e.g. it was an
* `UPDATE`). `result` will reference the same \ref connection object that `*this` references,
* and will be usable for server interaction as long as I/O object references to `*this` are
* valid.
* Executes the statement with the given parameters and reads the response into `result`.
*\n
* If the operation generated any rows, these __must__ be read (by using any of the
* `resultset::read_xxx` functions) before engaging in any further operation involving server
* communication. Otherwise, the results are undefined.
* After this operation completes successfully, `result.has_value() == true`.
*\n
* The statement actual parameters (`params`) are passed as a `std::tuple` of elements.
* See the `FieldLikeTuple` concept defition for more info.
* See the `FieldLikeTuple` concept defition for more info. You should pass exactly as many
* parameters as `this->num_params()`, or the operation will fail with an error.
* String parameters should be encoded using the connection's character set.
*\n
* This operation involves both reads and writes on the underlying stream.
*/
@ -104,22 +94,16 @@ public:
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void execute(
const FieldLikeTuple& params,
resultset<Stream>& result,
resultset& result,
error_code& err,
error_info& info
)
{
execute(params, execute_options(), result, err, info);
}
);
/// \copydoc execute
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void execute(const FieldLikeTuple& params, resultset<Stream>& result)
{
execute(params, execute_options(), result);
}
void execute(const FieldLikeTuple& params, resultset& result);
/**
* \copydoc execute
@ -138,80 +122,12 @@ public:
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
FieldLikeTuple&& params,
resultset<Stream>& result,
resultset& result,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_execute(
std::forward<FieldLikeTuple>(params),
execute_options(),
result,
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_execute
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
FieldLikeTuple&& params,
resultset<Stream>& result,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_execute(
std::forward<FieldLikeTuple>(params),
execute_options(),
result,
output_info,
std::forward<CompletionToken>(token)
);
}
/// \copydoc execute
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void execute(
const FieldLikeTuple& params,
const execute_options& opts,
resultset<Stream>& result,
error_code& err,
error_info& info
);
/// \copydoc execute
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void execute(
const FieldLikeTuple& params,
const execute_options& opts,
resultset<Stream>& result
);
/// \copydoc async_execute
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
FieldLikeTuple&& params,
const execute_options& opts,
resultset<Stream>& result,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_execute(
std::forward<FieldLikeTuple>(params),
opts,
result,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
@ -227,60 +143,134 @@ public:
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
FieldLikeTuple&& params,
const execute_options& opts,
resultset<Stream>& result,
resultset& result,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief (Experimental) Executes a prepared statement, passing parameters as a range.
* \brief Starts a statement execution as a multi-function operation.
* \details
* [warning This function is experimental. Details may change in the future without notice.]
* 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`).
*\n
* If the operation generated any rows, these <b>must</b> be read (by using \ref
*connection::read_one_row or \ref connection::read_some_rows) before engaging in any further
*operation involving server communication. Otherwise, the results are undefined.
*\n
* The statement actual parameters (`params`) are passed as a `std::tuple` of elements.
* See the `FieldLikeTuple` concept defition for more info. You should pass exactly as many
* parameters as `this->num_params()`, or the operation will fail with an error.
* String parameters should be encoded using the connection's character set.
*\n
* This operation involves both reads and writes on the underlying stream.
*/
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void start_execution(
const FieldLikeTuple& params,
execution_state& ex,
error_code& err,
error_info& info
);
/// \copydoc start_execution(const FieldLikeTuple&,execution_state&,error_code&,error_info&)
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
void start_execution(const FieldLikeTuple& params, execution_state& st);
/**
* \copydoc start_execution(const FieldLikeTuple&,execution_state&,error_code&,error_info&)
* \details
* If `CompletionToken` is deferred (like `use_awaitable`), and `params` contains any reference
* type (like `string_view`), the caller must keep the values pointed by these references alive
* until the operation is initiated. Value types will be copied/moved as required, so don't need
* to be kept alive.
*
* Starts a multi-function operation. This function will write the execute request to the
* server and read the initial server response, but won't read the generated rows, if any. After
* this operation completes, `result` will have \ref resultset::meta populated, and may become
* \ref resultset::complete, if the operation did not generate any rows (e.g. it was an
* `UPDATE`). `result` will reference the same \ref connection object that `*this` references,
* and will be usable for server interaction as long as I/O object references to `*this` are
* valid.
* The handler signature for this operation is `void(boost::mysql::error_code)`.
*/
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_start_execution(
FieldLikeTuple&& params,
execution_state& st,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_start_execution(
std::forward<FieldLikeTuple>(params),
st,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_start_execution(FieldLikeTuple&&,execution_state&,CompletionToken&&)
template <
BOOST_MYSQL_FIELD_LIKE_TUPLE FieldLikeTuple,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type),
class EnableIf = detail::enable_if_field_like_tuple<FieldLikeTuple>>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_start_execution(
FieldLikeTuple&& params,
execution_state& st,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Starts a statement execution as a multi-function operation.
* \details
* <b>Warning: this function is experimental. Details may change in the future without
* notice.</b>
*\n
* If the operation generated any rows, these __must__ be read (by using any of the
* `resultset::read_xxx` functions) before engaging in any further operation involving server
* communication. Otherwise, the results are undefined.
* 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`).
*\n
* The statement actual parameters are passed as an iterator range. There should be
* __exactly__ as many parameters as required (as given by \ref statement::num_params).
* Dereferencing the passed iterators should yield a type convertible to \ref field_view.
* Both \ref field and \ref field_view satisfy this.
* If the operation generated any rows, these <b>must</b> be read (by using \ref
* connection::read_one_row or \ref connection::read_some_rows) before engaging in any further
* operation involving server communication. Otherwise, the results are undefined.
*\n
* The statement actual parameters are passed as an iterator range.
* See the `FieldViewForwardIterator` concept defition for more info. You should pass exactly as
* many parameters as `this->num_params()`, or the operation will fail with an error. String
* parameters should be encoded using the connection's character set.
*\n
* This operation involves both reads and writes on the underlying stream.
*/
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void execute(
void start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& options,
resultset<Stream>& result,
execution_state& st,
error_code& ec,
error_info& info
);
/// \copydoc execute(FieldViewFwdIterator,FieldViewFwdIterator,const execute_options&,resultset<Stream>&,error_code&,error_info&)
/// \copydoc start_execution(FieldViewFwdIterator,FieldViewFwdIterator,execution_state&,error_code&,error_info&)
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
void execute(
void start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& options,
resultset<Stream>& result
execution_state& st
);
/**
* \copydoc execute(FieldViewFwdIterator,FieldViewFwdIterator,const execute_options&,resultset<Stream>&,error_code&,error_info&)
* \copydoc start_execution(FieldViewFwdIterator,FieldViewFwdIterator,execution_state&,error_code&,error_info&)
* \details
* If `CompletionToken` is deferred (like `use_awaitable`), the caller must keep objects in
* the range `\\[params_first, params_last)` alive until the operation is initiated.
* the iterator range alive until the operation is initiated.
*
* The handler signature for this operation is `void(boost::mysql::error_code)`.
*/
@ -289,41 +279,38 @@ public:
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
async_start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& options,
resultset<Stream>& result,
execution_state& st,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
)
{
return async_execute(
return async_start_execution(
params_first,
params_last,
options,
result,
st,
get_channel().shared_info(),
std::forward<CompletionToken>(token)
);
}
/// \copydoc async_execute(FieldViewFwdIterator,FieldViewFwdIterator,const execute_options&,resultset<Stream>&,CompletionToken&&)
/// \copydoc async_start_execution(FieldViewFwdIterator,FieldViewFwdIterator,execution_state&,CompletionToken&&)
template <
BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator,
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code))
CompletionToken BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute(
async_start_execution(
FieldViewFwdIterator params_first,
FieldViewFwdIterator params_last,
const execute_options& options,
resultset<Stream>& result,
execution_state& st,
error_info& output_info,
CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)
);
/**
* \brief Closes a prepared statement, deallocating it from the server.
* \brief Closes a statement, deallocating it from the server.
* \details
* After this operation succeeds, `this->valid()` will return `false`, and no further functions
* may be called on this prepared statement, other than assignment.

View File

@ -8,9 +8,10 @@
#ifndef BOOST_MYSQL_STATEMENT_BASE_HPP
#define BOOST_MYSQL_STATEMENT_BASE_HPP
#include <boost/mysql/detail/protocol/prepared_statement_messages.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/detail/protocol/prepared_statement_messages.hpp>
#include <array>
#include <cstdint>
#include <tuple>
@ -18,9 +19,6 @@
namespace boost {
namespace mysql {
/// Convenience constant to use when executing a statement without parameters.
constexpr std::tuple<> no_statement_params{};
/**
* \brief The base class for prepared statements.
* \details Don't instantiate this class directly - use \ref statement instead.
@ -47,11 +45,11 @@ public:
* \brief Returns `true` if the object represents an actual server statement.
* \details Calling any function other than assignment on a statement for which
* this function returns `false` results in undefined behavior.
*
*\n
* To be usable for server communication, the \ref connection referenced by this object must be
* alive and open, too.
*
* Returns `false` for default-constructed, moved-from and closed statements.
*\n
* Returns `false` for default-constructed and closed statements.
*/
bool valid() const noexcept { return channel_ != nullptr; }

View File

@ -21,9 +21,6 @@ using tcp_connection = connection<boost::asio::ip::tcp::socket>;
/// The statement type to use with \ref tcp_connection.
using tcp_statement = typename tcp_connection::statement_type;
/// The resultset type to use with \ref tcp_connection.
using tcp_resultset = typename tcp_connection::resultset_type;
} // namespace mysql
} // namespace boost

View File

@ -22,9 +22,6 @@ using tcp_ssl_connection = connection<boost::asio::ssl::stream<boost::asio::ip::
/// The statement type to use with \ref tcp_ssl_connection.
using tcp_ssl_statement = typename tcp_ssl_connection::statement_type;
/// The resultset type to use with \ref tcp_ssl_connection.
using tcp_ssl_resultset = typename tcp_ssl_connection::resultset_type;
} // namespace mysql
} // namespace boost

View File

@ -23,9 +23,6 @@ using unix_connection = connection<boost::asio::local::stream_protocol::socket>;
/// The statement type to use with \ref unix_connection.
using unix_statement = typename unix_connection::statement_type;
/// The resultset type to use with \ref unix_connection.
using unix_resultset = typename unix_connection::resultset_type;
#endif
} // namespace mysql

View File

@ -25,9 +25,6 @@ using unix_ssl_connection = connection<
/// The statement type to use with \ref unix_ssl_connection.
using unix_ssl_statement = typename unix_ssl_connection::statement_type;
/// The resultset type to use with \ref unix_ssl_connection.
using unix_ssl_resultset = typename unix_ssl_connection::resultset_type;
#endif
} // namespace mysql

View File

@ -1,27 +0,0 @@
//
// 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)
//
#ifndef BOOST_MYSQL_USE_VIEWS_HPP
#define BOOST_MYSQL_USE_VIEWS_HPP
namespace boost {
namespace mysql {
/**
* \brief Placeholder type to indicate that a function should return non-owning views.
*/
struct use_views_t
{
};
/// Placeholder to indicate that a function should return non-owning views.
constexpr use_views_t use_views{};
} // namespace mysql
} // namespace boost
#endif

View File

@ -4,7 +4,7 @@
"authors": [
"Rubén Pérez"
],
"description": "Async MySQL client",
"description": "MySQL client library built on top of Boost.Asio.",
"category": [
"Concurrent",
"IO"
@ -13,4 +13,4 @@
"Rubén Pérez <rubenperez038@gmail.com>"
],
"cxxstd": "11"
}
}

View File

@ -6,11 +6,114 @@
#
import os ;
import sequence ;
import path ;
project /boost/mysql/test ;
# System libraries
if [ os.name ] = NT
{
local OPENSSL_ROOT_ENV = [ os.environ OPENSSL_ROOT ] ;
local OPENSSL_ROOT = "" ;
if $(OPENSSL_ROOT_ENV)
{
OPENSSL_ROOT = $(OPENSSL_ROOT_ENV) ;
}
else
{
OPENSSL_ROOT = "C:/OpenSSL" ;
}
local openssl_requirements =
<include>$(OPENSSL_ROOT)/include
<library-path>$(OPENSSL_ROOT)/lib
;
if [ path.exists $(OPENSSL_ROOT)/lib/libssl.lib ]
{
echo "OpenSSL > 1.1.0. Including libssl" ;
lib ssl : : <target-os>windows <name>libssl : : $(openssl_requirements) ;
}
else if [ path.exists $(OPENSSL_ROOT)/lib/ssleay32.lib ]
{
echo "OpenSSL < 1.1.0. Including ssleay32" ;
lib ssl : : <target-os>windows <name>ssleay32 : : $(openssl_requirements) ;
}
else
{
lib ssl : : <link>shared : : $(openssl_requirements) ;
}
if [ path.exists $(OPENSSL_ROOT)/lib/libcrypto.lib ]
{
echo "OpenSSL > 1.1.0. Including libcrypto" ;
lib crypto : : <target-os>windows <name>libcrypto : : $(openssl_requirements) ;
}
else if [ path.exists $(OPENSSL_ROOT)/lib/libeay32.lib ]
{
echo "OpenSSL < 1.1.0. Including libeay32" ;
lib crypto : : <target-os>windows <name>libeay32 : : $(openssl_requirements) ;
}
else
{
lib crypto : : <link>shared : : $(openssl_requirements) ;
}
}
else
{
local OPENSSL_ROOT = [ os.environ OPENSSL_ROOT ] ;
local openssl_requirements =
<include>$(OPENSSL_ROOT)/include
<library-path>$(OPENSSL_ROOT)/lib
;
lib ssl : : <link>shared : : $(openssl_requirements) ;
lib crypto : : <link>shared : : $(openssl_requirements) ;
}
# Requirements to use across targets
local requirements =
<include>../include
<define>BOOST_ALL_NO_LIB=1
<define>BOOST_ASIO_NO_DEPRECATED=1
<define>BOOST_ASIO_DISABLE_BOOST_ARRAY=1
<define>BOOST_ASIO_DISABLE_BOOST_BIND=1
<define>BOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
<define>BOOST_ASIO_DISABLE_BOOST_REGEX=1
<define>BOOST_ASIO_HAS_DEFAULT_FUNCTION_TEMPLATE_ARGUMENTS=1
<define>BOOST_COROUTINES_NO_DEPRECATION_WARNING=1
<define>BOOST_ALLOW_DEPRECATED_HEADERS=1
<toolset>msvc:<cxxflags>"/bigobj /Zc:__cplusplus"
<toolset>msvc-14.1:<cxxflags>"/permissive-"
<toolset>msvc-14.2:<cxxflags>"/permissive-"
<toolset>msvc:<define>_SCL_SECURE_NO_WARNINGS=1
<toolset>msvc:<define>_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING
<toolset>msvc:<define>_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING
<toolset>msvc:<define>_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
<target-os>linux:<define>_XOPEN_SOURCE=600
<target-os>linux:<define>_GNU_SOURCE=1
<target-os>windows:<define>_WIN32_WINNT=0x0601
;
# A static Asio library to speed up builds
lib asio_separate_build
:
../tools/asio.cpp
ssl
crypto
:
<link>static
$(requirements)
<define>BOOST_ASIO_SEPARATE_COMPILATION
:
:
$(requirements)
<define>BOOST_ASIO_SEPARATE_COMPILATION
;
alias mysql : asio_separate_build ;
alias test_common
:
/boost/mysql//mysql
mysql
/boost/test//boost_unit_test_framework
:
<link>static
@ -18,139 +121,3 @@ alias test_common
:
<include>common
;
# Unit tests
lib unittests_entry_point
:
unit/entry_point.cpp
test_common
:
<link>static
;
cpp-pch unittests_pch
:
unit/pch.hpp
unittests_entry_point
:
<include>unit
;
unit-test boost_mysql_unittests
:
unittests_pch
unittests_entry_point
unit/detail/channel/read_buffer.cpp
unit/detail/channel/message_parser.cpp
unit/detail/channel/message_reader.cpp
unit/detail/channel/message_writer_processor.cpp
unit/detail/channel/message_writer.cpp
unit/detail/auth/auth_calculator.cpp
unit/detail/auxiliar/static_string.cpp
unit/detail/auxiliar/rows_iterator.cpp
unit/detail/auxiliar/field_type_traits.cpp
unit/detail/auxiliar/datetime.cpp
unit/detail/network_algorithms/read_one_row.cpp
unit/detail/network_algorithms/read_some_rows.cpp
unit/detail/network_algorithms/read_all_rows.cpp
unit/detail/protocol/capabilities.cpp
unit/detail/protocol/null_bitmap_traits.cpp
unit/detail/protocol/serialization_test.cpp
unit/detail/protocol/deserialize_text_field.cpp
unit/detail/protocol/deserialize_binary_field.cpp
unit/detail/protocol/deserialize_row.cpp
unit/date.cpp
unit/datetime.cpp
unit/field_view.cpp
unit/field.cpp
unit/row_view.cpp
unit/row.cpp
unit/rows_view.cpp
unit/rows.cpp
unit/metadata.cpp
unit/metadata_collection_view.cpp
unit/error.cpp
unit/statement.cpp
unit/resultset.cpp
unit/regressions.cpp
unit/connection.cpp
:
<toolset>msvc:<cxxflags>-FI"pch.hpp" # https://github.com/boostorg/boost/issues/711
;
# 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
lib integrationtests_entry_point :
integration/entry_point.cpp
test_common
:
<link>static
;
cpp-pch integrationtests_pch
:
integration/pch.hpp
/boost/coroutine//boost_coroutine
integrationtests_entry_point
:
<include>integration
;
unit-test boost_mysql_integrationtests
:
integrationtests_pch
/boost/coroutine//boost_coroutine
integrationtests_entry_point
# 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/statement_lifecycle.cpp
integration/resultset.cpp
integration/quit_connection.cpp
integration/close_connection.cpp
integration/reconnect.cpp
integration/database_types.cpp
:
<testing.arg>$(test_command)
<include>integration/utils/include
<toolset>msvc:<cxxflags>-FI"pch.hpp" # https://github.com/boostorg/boost/issues/711
;
explicit boost_mysql_integrationtests ;

Some files were not shown because too many files have changed in this diff Show More