Skip to content

Commit c343cde

Browse files
authored
Improved discussion and examples
Added a tutorial on UPDATEs, transactions and multi-queries Added a tutorial on connection_pool Added a tutorial on error handling Added examples on INSERTs and DELETEs Rewrote the discussion page on character sets Added a discussion page on the templated connection class Removed superseded examples on timeouts and multi-queries Updated the coverage build to gcc-14 (gcc-13 was using a non-LTS release that caused problems) Contributes to #365 and #366
1 parent ef9224c commit c343cde

31 files changed

+2805
-437
lines changed

doc/qbk/00_main.qbk

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
[template reflink[id][reflink2 [id] [id]]]
3232
[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]]
3333
[template refmemunq[class mem][reflink2 [class].[mem] [mem]]]
34-
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]]
34+
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^asio::[term]]]]
3535
[template mysqllink[id text][@https://dev.mysql.com/doc/refman/8.0/en/[id] [text]]]
3636

3737
[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]]
@@ -129,9 +129,12 @@ END
129129
[import ../../example/1_tutorial/2_async.cpp]
130130
[import ../../example/1_tutorial/3_with_params.cpp]
131131
[import ../../example/1_tutorial/4_static_interface.cpp]
132+
[import ../../example/1_tutorial/5_updates_transactions.cpp]
133+
[import ../../example/1_tutorial/6_connection_pool.cpp]
134+
[import ../../example/1_tutorial/7_error_handling.cpp]
135+
[import ../../example/2_simple/inserts.cpp]
136+
[import ../../example/2_simple/deletes.cpp]
132137
[import ../../example/2_simple/prepared_statements.cpp]
133-
[import ../../example/2_simple/timeouts.cpp]
134-
[import ../../example/2_simple/multi_queries_transactions.cpp]
135138
[import ../../example/2_simple/disable_tls.cpp]
136139
[import ../../example/2_simple/tls_certificate_verification.cpp]
137140
[import ../../example/2_simple/metadata.cpp]
@@ -160,6 +163,7 @@ END
160163
[import ../../test/integration/test/snippets/sql_formatting_custom.cpp]
161164
[import ../../test/integration/test/snippets/multi_function.cpp]
162165
[import ../../test/integration/test/snippets/tutorials.cpp]
166+
[import ../../test/integration/test/snippets/templated_connection.cpp]
163167
[import ../../test/integration/test/snippets/metadata.cpp]
164168
[import ../../test/integration/test/snippets/connection_pool.cpp]
165169
[import ../../test/integration/test/snippets/time_types.cpp]
@@ -178,6 +182,9 @@ END
178182
[include 03_2_tutorial_async.qbk]
179183
[include 03_3_tutorial_with_params.qbk]
180184
[include 03_4_tutorial_static_interface.qbk]
185+
[include 03_5_tutorial_updates_transactions.qbk]
186+
[include 03_6_tutorial_connection_pool.qbk]
187+
[include 03_7_tutorial_error_handling.qbk]
181188
[include 04_overview.qbk]
182189
[include 05_connection_establishment.qbk]
183190
[include 06_sql_formatting.qbk]
@@ -193,8 +200,7 @@ END
193200
[include 16_metadata.qbk]
194201
[include 17_charsets.qbk]
195202
[include 18_time_types.qbk]
196-
[/ TODO: re-enable this
197-
[include 19_templated_connection.qbk] ]
203+
[include 19_templated_connection.qbk]
198204
[include 20_pipeline.qbk]
199205
[include 21_examples.qbk]
200206

doc/qbk/03_4_tutorial_static_interface.qbk

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ The mechanics are quite similar to what's been explained here.
101101

102102
Full program listing for this tutorial is [link mysql.examples.tutorial_static_interface here].
103103

104-
This concludes our tutorial series. You can now look at the [link mysql.overview overview section]
105-
to learn more about the library features, or to the [link mysql.examples example section]
106-
if you prefer to learn by doing.
104+
You can now proceed to [link mysql.tutorial_updates_transactions the next tutorial].
107105

108106
[endsect]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
[/
2+
Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
3+
4+
Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
]
7+
8+
[section:tutorial_updates_transactions Tutorial 5: UPDATEs, transactions and semicolon-separated queries]
9+
10+
All the previous tutorials have only used `SELECT` statements, but
11+
Boost.MySQL is not limited to them. Using [refmemunq any_connection async_execute]
12+
you can run any SQL statement supported by MySQL.
13+
14+
In this tutorial, we will write a program that changes the first name of an
15+
employee, given their ID, and prints the updated employee details.
16+
We will use an `UPDATE` and transaction management statements.
17+
`INSERT` and `DELETE` statements have similar mechanics.
18+
19+
20+
21+
[heading A simple UPDATE]
22+
23+
We can use the same tools and functions as in previous tutorials:
24+
25+
[tutorial_updates_transactions_update]
26+
27+
By default, auto-commit is enabled, meaning that when `async_execute`
28+
returns, the `UPDATE` is visible to other client connections.
29+
30+
31+
32+
33+
[heading Checking that the UPDATE took effect]
34+
35+
The above query will succeed even if there was no employee with the given ID.
36+
We would like to retrieve the updated employee details on success, and emit
37+
a useful error message if no employee was matched.
38+
39+
We may be tempted to use [refmem results affected_rows] at first, but
40+
this doesn't convey the information we're looking for:
41+
a row may be matched but not affected. For example, if you try to
42+
set `first_name` to the same value it already has,
43+
MySQL will count the row as a matched, but not affected.
44+
45+
46+
MySQL does not support the `UPDATE ... RETURNING` syntax, so we will
47+
have to retrieve the employee manually after updating it.
48+
We can add the following after our `UPDATE`:
49+
50+
[tutorial_updates_transactions_select]
51+
52+
However, the code above contains a race condition. Imagine the following situation:
53+
54+
* The `UPDATE` is issued. No employee is matched.
55+
* Before our program sends the `SELECT` query, a different program inserts
56+
an employee with the ID that we're trying to update.
57+
* Our program runs the `SELECT` statement and retrieves the newly inserted row.
58+
59+
To our program, it looks like we succeeded performing the update, when
60+
we really didn't. Depending on the nature of our program, this may
61+
or may not have serious consequences, but it's something we should avoid.
62+
63+
64+
[heading Avoiding the race condition with a transaction block]
65+
66+
We can fix the race condition using transactions.
67+
In MySQL, a transaction block is opened with `START TRANSACTION`.
68+
Subsequent statements will belong to the transaction block,
69+
until the transaction either commits or is rolled back.
70+
A `COMMIT` statement commits the transaction.
71+
A rollback happens if the connection that initiated the transaction
72+
closes or an explicit `ROLLBACK` statement is used.
73+
74+
We will enclose our `UPDATE` and `SELECT` statements in
75+
a transaction block. This will ensure that the `SELECT`
76+
will get the updated row, if any:
77+
78+
[tutorial_updates_transactions_txn]
79+
80+
81+
82+
83+
[heading Using multi-queries]
84+
85+
While the code we've written is correct, it's not very performant.
86+
We're incurring in 4 round-trips to the server, when our queries don't depend
87+
on the result of previous ones. The round-trips occur within a transaction
88+
block, which causes certain database rows to be locked, increasing contention.
89+
We can improve the situation by running our four statements in a single batch.
90+
91+
Multi-queries are a protocol feature that lets you execute several queries
92+
at once. Individual queries must be separated by semicolons.
93+
94+
Multi-queries are disabled by default. To enable them, set
95+
[refmem connect_params multi_queries] to `true` before connecting:
96+
97+
[tutorial_updates_transactions_connect]
98+
99+
Multi-queries can be composed an executed using the same
100+
functions we've been using:
101+
102+
[tutorial_updates_transactions_multi_queries]
103+
104+
Accessing the results is slightly different. MySQL returns 4 resultsets,
105+
one for each query. In Boost.MySQL, this operation is said to be
106+
[link mysql.multi_resultset multi-resultset].
107+
[reflink results] can actually store more than one resultset.
108+
[refmem results rows] actually accesses the rows in the first resultset,
109+
because it's the most common use case.
110+
111+
We want to get the rows retrieved by the `SELECT` statement,
112+
which corresponds to the third resultset.
113+
[refmem results at] returns a [reflink resultset_view] containing data
114+
for the requested resultset:
115+
116+
[tutorial_updates_transactions_dynamic_results]
117+
118+
119+
[heading Using manual indices in with_params]
120+
121+
Repeating `employee_id` in the parameter list passed to `with_params`
122+
violates the DRY principle.
123+
As with `std::format`, we can refer to a format argument more than once
124+
by using manual indices:
125+
126+
[tutorial_updates_transactions_manual_indices]
127+
128+
129+
130+
[heading Using the static interface with multi-resultset]
131+
132+
Finally, we can rewrite our code to use the static interface so it's safer.
133+
In multi-resultset scenarios, we can pass as many row types
134+
to [reflink static_results] as resultsets we expect.
135+
We can use the empty tuple (`std::tuple<>`) as a row type
136+
for operations that don't return rows, like the `UPDATE`.
137+
Our code becomes:
138+
139+
[tutorial_updates_transactions_static]
140+
141+
142+
[heading Wrapping up]
143+
144+
Full program listing for this tutorial is [link mysql.examples.tutorial_updates_transactions here].
145+
146+
You can now proceed to [link mysql.tutorial_connection_pool the next tutorial].
147+
148+
149+
[endsect]
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
[/
2+
Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
3+
4+
Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
]
7+
8+
[section:tutorial_connection_pool Tutorial 6: Connection pools]
9+
10+
All our programs until now have used one-shot connections.
11+
They also didn't feature any fault tolerance:
12+
if the server is unavailable, our program throws an exception
13+
and terminates. Most real world scenarios require
14+
long-lived, reliable connections, instead.
15+
16+
In this tutorial, we will implement a server for a simple request-reply
17+
protocol. The protocol allows clients to retrieve the full name of
18+
an employee given their ID.
19+
We will use [reflink connection_pool] to maintain a set of healthy connections
20+
that we can use when a client connects to our server.
21+
22+
23+
24+
25+
[heading The protocol]
26+
27+
The protocol is TCP based, and can be described as follows:
28+
29+
* After connecting, the client sends a message containing the employee ID,
30+
encoded as an 8-byte, big-endian integer.
31+
* The server replies with a string containing the employee full name,
32+
or "NOT_FOUND", if the ID doesn't match any employee.
33+
* The connection is closed after that.
34+
35+
This protocol is intentionally overly simplistic, and
36+
shouldn't be used in production. See our
37+
[link mysql.examples.connection_pool HTTP examples]
38+
for more advanced use cases.
39+
40+
41+
42+
43+
[heading Creating a connection pool]
44+
45+
[reflink connection_pool] is an I/O object that contains
46+
[reflink any_connection] objects, and can be
47+
constructed from an execution context and a [reflink pool_params]
48+
config struct:
49+
50+
[tutorial_connection_pool_create]
51+
52+
A single connection pool is usually created per application.
53+
54+
[refmem connection_pool async_run] should be called once per pool:
55+
56+
[tutorial_connection_pool_run]
57+
58+
59+
60+
61+
62+
63+
[heading Using pooled connections]
64+
65+
Let's first write a coroutine that encapsulates database access.
66+
Given an employee ID, it should return the string to be sent as response to the client.
67+
Don't worry about error handling for now - we will take care of it in the next tutorial.
68+
69+
When using a pool, we don't need to explicitly create, connect or close connections.
70+
Instead, we use [refmem connection_pool async_get_connection] to obtain them from the pool:
71+
72+
[tutorial_connection_pool_get_connection]
73+
74+
[reflink pooled_connection] is a wrapper around [reflink any_connection],
75+
with some pool-specific additions. We can use it like a regular connection:
76+
77+
[tutorial_connection_pool_use]
78+
79+
When a [reflink pooled_connection] is destroyed, the connection is returned
80+
to the pool. The underlying connection will be cleaned up using a lightweight
81+
session reset mechanism and recycled.
82+
Subsequent [refmemunq connection_pool async_get_connection]
83+
calls may retrieve the same connection. This improves efficiency,
84+
since session establishment is costly.
85+
86+
[refmemunq connection_pool async_get_connection] waits
87+
for a client connection to become available before completing.
88+
If the server is unavailable or credentials are invalid,
89+
it may wait indefinitely. This is a problem for both development and production.
90+
We can solve this by using [asioreflink cancel_after cancel_after],
91+
which allows setting timeouts to async operations:
92+
93+
[tutorial_connection_pool_get_connection_timeout]
94+
95+
Don't worry if you don't fully understand how this works.
96+
We will go into more detail on [asioreflink cancel_after cancel_after],
97+
cancellations and completion tokens in the next tutorial.
98+
99+
Putting all pieces together, our coroutine becomes:
100+
101+
[tutorial_connection_pool_db]
102+
103+
104+
105+
106+
[heading Handling a client session]
107+
108+
Let's now build a function that handles a client sessions,
109+
invoking the database access logic in the process:
110+
111+
[tutorial_connection_pool_session]
112+
113+
114+
115+
116+
[heading Listening for connections]
117+
118+
We now need logic to accept incoming TCP connections.
119+
We will use an `asio::ip::tcp::acceptor` object
120+
to accomplish it, listening for connections in a loop
121+
until the server is stopped:
122+
123+
[tutorial_connection_pool_listener]
124+
125+
126+
127+
128+
[heading Waiting for signals]
129+
130+
Finally, we need a way to stop our program. We will use an `asio::signal_set` object
131+
to catch signals, and call `io_context::stop` when Ctrl-C is pressed:
132+
133+
[tutorial_connection_pool_signals]
134+
135+
Putting all these pieces together, our main program becomes:
136+
137+
[tutorial_connection_pool_main]
138+
139+
140+
141+
142+
[heading Wrapping up]
143+
144+
Full program listing for this tutorial is [link mysql.examples.tutorial_connection_pool here].
145+
146+
For simplicity, we've left error handling out of this tutorial.
147+
This is usually very important in a server like the one we've written,
148+
and is the topic of our [link mysql.tutorial_error_handling next tutorial].
149+
150+
[endsect]

0 commit comments

Comments
 (0)