Skip to content

Commit

Permalink
Improved discussion and examples
Browse files Browse the repository at this point in the history
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
  • Loading branch information
anarthal authored Nov 29, 2024
1 parent ef9224c commit c343cde
Show file tree
Hide file tree
Showing 31 changed files with 2,805 additions and 437 deletions.
16 changes: 11 additions & 5 deletions doc/qbk/00_main.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
[template reflink[id][reflink2 [id] [id]]]
[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]]
[template refmemunq[class mem][reflink2 [class].[mem] [mem]]]
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]]
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^asio::[term]]]]
[template mysqllink[id text][@https://dev.mysql.com/doc/refman/8.0/en/[id] [text]]]

[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]]
Expand Down Expand Up @@ -129,9 +129,12 @@ END
[import ../../example/1_tutorial/2_async.cpp]
[import ../../example/1_tutorial/3_with_params.cpp]
[import ../../example/1_tutorial/4_static_interface.cpp]
[import ../../example/1_tutorial/5_updates_transactions.cpp]
[import ../../example/1_tutorial/6_connection_pool.cpp]
[import ../../example/1_tutorial/7_error_handling.cpp]
[import ../../example/2_simple/inserts.cpp]
[import ../../example/2_simple/deletes.cpp]
[import ../../example/2_simple/prepared_statements.cpp]
[import ../../example/2_simple/timeouts.cpp]
[import ../../example/2_simple/multi_queries_transactions.cpp]
[import ../../example/2_simple/disable_tls.cpp]
[import ../../example/2_simple/tls_certificate_verification.cpp]
[import ../../example/2_simple/metadata.cpp]
Expand Down Expand Up @@ -160,6 +163,7 @@ END
[import ../../test/integration/test/snippets/sql_formatting_custom.cpp]
[import ../../test/integration/test/snippets/multi_function.cpp]
[import ../../test/integration/test/snippets/tutorials.cpp]
[import ../../test/integration/test/snippets/templated_connection.cpp]
[import ../../test/integration/test/snippets/metadata.cpp]
[import ../../test/integration/test/snippets/connection_pool.cpp]
[import ../../test/integration/test/snippets/time_types.cpp]
Expand All @@ -178,6 +182,9 @@ END
[include 03_2_tutorial_async.qbk]
[include 03_3_tutorial_with_params.qbk]
[include 03_4_tutorial_static_interface.qbk]
[include 03_5_tutorial_updates_transactions.qbk]
[include 03_6_tutorial_connection_pool.qbk]
[include 03_7_tutorial_error_handling.qbk]
[include 04_overview.qbk]
[include 05_connection_establishment.qbk]
[include 06_sql_formatting.qbk]
Expand All @@ -193,8 +200,7 @@ END
[include 16_metadata.qbk]
[include 17_charsets.qbk]
[include 18_time_types.qbk]
[/ TODO: re-enable this
[include 19_templated_connection.qbk] ]
[include 19_templated_connection.qbk]
[include 20_pipeline.qbk]
[include 21_examples.qbk]

Expand Down
4 changes: 1 addition & 3 deletions doc/qbk/03_4_tutorial_static_interface.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ The mechanics are quite similar to what's been explained here.

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

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

[endsect]
149 changes: 149 additions & 0 deletions doc/qbk/03_5_tutorial_updates_transactions.qbk
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
[/
Copyright (c) 2019-2024 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:tutorial_updates_transactions Tutorial 5: UPDATEs, transactions and semicolon-separated queries]

All the previous tutorials have only used `SELECT` statements, but
Boost.MySQL is not limited to them. Using [refmemunq any_connection async_execute]
you can run any SQL statement supported by MySQL.

In this tutorial, we will write a program that changes the first name of an
employee, given their ID, and prints the updated employee details.
We will use an `UPDATE` and transaction management statements.
`INSERT` and `DELETE` statements have similar mechanics.



[heading A simple UPDATE]

We can use the same tools and functions as in previous tutorials:

[tutorial_updates_transactions_update]

By default, auto-commit is enabled, meaning that when `async_execute`
returns, the `UPDATE` is visible to other client connections.




[heading Checking that the UPDATE took effect]

The above query will succeed even if there was no employee with the given ID.
We would like to retrieve the updated employee details on success, and emit
a useful error message if no employee was matched.

We may be tempted to use [refmem results affected_rows] at first, but
this doesn't convey the information we're looking for:
a row may be matched but not affected. For example, if you try to
set `first_name` to the same value it already has,
MySQL will count the row as a matched, but not affected.


MySQL does not support the `UPDATE ... RETURNING` syntax, so we will
have to retrieve the employee manually after updating it.
We can add the following after our `UPDATE`:

[tutorial_updates_transactions_select]

However, the code above contains a race condition. Imagine the following situation:

* The `UPDATE` is issued. No employee is matched.
* Before our program sends the `SELECT` query, a different program inserts
an employee with the ID that we're trying to update.
* Our program runs the `SELECT` statement and retrieves the newly inserted row.

To our program, it looks like we succeeded performing the update, when
we really didn't. Depending on the nature of our program, this may
or may not have serious consequences, but it's something we should avoid.


[heading Avoiding the race condition with a transaction block]

We can fix the race condition using transactions.
In MySQL, a transaction block is opened with `START TRANSACTION`.
Subsequent statements will belong to the transaction block,
until the transaction either commits or is rolled back.
A `COMMIT` statement commits the transaction.
A rollback happens if the connection that initiated the transaction
closes or an explicit `ROLLBACK` statement is used.

We will enclose our `UPDATE` and `SELECT` statements in
a transaction block. This will ensure that the `SELECT`
will get the updated row, if any:

[tutorial_updates_transactions_txn]




[heading Using multi-queries]

While the code we've written is correct, it's not very performant.
We're incurring in 4 round-trips to the server, when our queries don't depend
on the result of previous ones. The round-trips occur within a transaction
block, which causes certain database rows to be locked, increasing contention.
We can improve the situation by running our four statements in a single batch.

Multi-queries are a protocol feature that lets you execute several queries
at once. Individual queries must be separated by semicolons.

Multi-queries are disabled by default. To enable them, set
[refmem connect_params multi_queries] to `true` before connecting:

[tutorial_updates_transactions_connect]

Multi-queries can be composed an executed using the same
functions we've been using:

[tutorial_updates_transactions_multi_queries]

Accessing the results is slightly different. MySQL returns 4 resultsets,
one for each query. In Boost.MySQL, this operation is said to be
[link mysql.multi_resultset multi-resultset].
[reflink results] can actually store more than one resultset.
[refmem results rows] actually accesses the rows in the first resultset,
because it's the most common use case.

We want to get the rows retrieved by the `SELECT` statement,
which corresponds to the third resultset.
[refmem results at] returns a [reflink resultset_view] containing data
for the requested resultset:

[tutorial_updates_transactions_dynamic_results]


[heading Using manual indices in with_params]

Repeating `employee_id` in the parameter list passed to `with_params`
violates the DRY principle.
As with `std::format`, we can refer to a format argument more than once
by using manual indices:

[tutorial_updates_transactions_manual_indices]



[heading Using the static interface with multi-resultset]

Finally, we can rewrite our code to use the static interface so it's safer.
In multi-resultset scenarios, we can pass as many row types
to [reflink static_results] as resultsets we expect.
We can use the empty tuple (`std::tuple<>`) as a row type
for operations that don't return rows, like the `UPDATE`.
Our code becomes:

[tutorial_updates_transactions_static]


[heading Wrapping up]

Full program listing for this tutorial is [link mysql.examples.tutorial_updates_transactions here].

You can now proceed to [link mysql.tutorial_connection_pool the next tutorial].


[endsect]
150 changes: 150 additions & 0 deletions doc/qbk/03_6_tutorial_connection_pool.qbk
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
[/
Copyright (c) 2019-2024 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:tutorial_connection_pool Tutorial 6: Connection pools]

All our programs until now have used one-shot connections.
They also didn't feature any fault tolerance:
if the server is unavailable, our program throws an exception
and terminates. Most real world scenarios require
long-lived, reliable connections, instead.

In this tutorial, we will implement a server for a simple request-reply
protocol. The protocol allows clients to retrieve the full name of
an employee given their ID.
We will use [reflink connection_pool] to maintain a set of healthy connections
that we can use when a client connects to our server.




[heading The protocol]

The protocol is TCP based, and can be described as follows:

* After connecting, the client sends a message containing the employee ID,
encoded as an 8-byte, big-endian integer.
* The server replies with a string containing the employee full name,
or "NOT_FOUND", if the ID doesn't match any employee.
* The connection is closed after that.

This protocol is intentionally overly simplistic, and
shouldn't be used in production. See our
[link mysql.examples.connection_pool HTTP examples]
for more advanced use cases.




[heading Creating a connection pool]

[reflink connection_pool] is an I/O object that contains
[reflink any_connection] objects, and can be
constructed from an execution context and a [reflink pool_params]
config struct:

[tutorial_connection_pool_create]

A single connection pool is usually created per application.

[refmem connection_pool async_run] should be called once per pool:

[tutorial_connection_pool_run]






[heading Using pooled connections]

Let's first write a coroutine that encapsulates database access.
Given an employee ID, it should return the string to be sent as response to the client.
Don't worry about error handling for now - we will take care of it in the next tutorial.

When using a pool, we don't need to explicitly create, connect or close connections.
Instead, we use [refmem connection_pool async_get_connection] to obtain them from the pool:

[tutorial_connection_pool_get_connection]

[reflink pooled_connection] is a wrapper around [reflink any_connection],
with some pool-specific additions. We can use it like a regular connection:

[tutorial_connection_pool_use]

When a [reflink pooled_connection] is destroyed, the connection is returned
to the pool. The underlying connection will be cleaned up using a lightweight
session reset mechanism and recycled.
Subsequent [refmemunq connection_pool async_get_connection]
calls may retrieve the same connection. This improves efficiency,
since session establishment is costly.

[refmemunq connection_pool async_get_connection] waits
for a client connection to become available before completing.
If the server is unavailable or credentials are invalid,
it may wait indefinitely. This is a problem for both development and production.
We can solve this by using [asioreflink cancel_after cancel_after],
which allows setting timeouts to async operations:

[tutorial_connection_pool_get_connection_timeout]

Don't worry if you don't fully understand how this works.
We will go into more detail on [asioreflink cancel_after cancel_after],
cancellations and completion tokens in the next tutorial.

Putting all pieces together, our coroutine becomes:

[tutorial_connection_pool_db]




[heading Handling a client session]

Let's now build a function that handles a client sessions,
invoking the database access logic in the process:

[tutorial_connection_pool_session]




[heading Listening for connections]

We now need logic to accept incoming TCP connections.
We will use an `asio::ip::tcp::acceptor` object
to accomplish it, listening for connections in a loop
until the server is stopped:

[tutorial_connection_pool_listener]




[heading Waiting for signals]

Finally, we need a way to stop our program. We will use an `asio::signal_set` object
to catch signals, and call `io_context::stop` when Ctrl-C is pressed:

[tutorial_connection_pool_signals]

Putting all these pieces together, our main program becomes:

[tutorial_connection_pool_main]




[heading Wrapping up]

Full program listing for this tutorial is [link mysql.examples.tutorial_connection_pool here].

For simplicity, we've left error handling out of this tutorial.
This is usually very important in a server like the one we've written,
and is the topic of our [link mysql.tutorial_error_handling next tutorial].

[endsect]
Loading

0 comments on commit c343cde

Please sign in to comment.